多线程,特点,随机性,单核cpu并发一会执行这个线程,一会执行另外一个线程,造成代码随机性的现象
Java使用java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码,Java使用线程执行体来代表这段程序流。
步骤如下:
定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
创建Thread子类的实例对象,即创建了线程对象。
调用线程对象的start()方法来启动该线程。
代码:
1 public class Demo { 2 public static void main(String[] args) { 3 //创建线程对象 4 Thread thread = new TestThread("线程对象"); 5 //开启线程 6 thread.start(); 7 for (int i = 0; i < 10; i++) { 8 System.out.println("主线程" + i); 9 } 10 } 11 } 12 13 class TestThread extends Thread { 14 15 public TestThread(String name) { 16 //调用父类Thread的构造方法 17 super(name); 18 } 19 20 /** 21 * 重写run方法 22 */ 23 @Override 24 public void run() { 25 for (int i = 0; i < 10; i++) { 26 System.out.println(getName() + ":" + i); 27 } 28 } 29 }
结果:
主线程0
线程对象:0
线程对象:1
主线程1
线程对象:2
线程对象:3
主线程2
线程对象:4
主线程3
线程对象:5
主线程4
线程对象:6
主线程5
线程对象:7
线程对象:8
线程对象:9
主线程6
主线程7
主线程8
主线程9
创建对象,给Thread类里面成员变量字符数组进行赋值,线程的名字只是把这个字符数组变成字符串
注意jdk1.9把字符串数组变成了字符串
分配一个新的线程对象。默认名字第一个子线程叫Thread-0,其他以此类推
public Thread()
分配一个指定名字的新的线程对象。
public Thread(String name)
分配一个带有指定目标任务的,新的线程对象,默认名字第一个子线程叫Thread-0,其他以此类推
public Thread(Runnable target)
分配一个带有指定目标任务的,新的线程对象,并指定线程的名字。
public Thread(Runnable target,String name)
获取当前线程的名字
public String getName()
设置当前线程的名字
public String setName()
导致此线程开始执行; Java虚拟机调用此线程的run方法。同一个线程对象不能多次调用start方法,否则会报非法线程状态异常:IllegalThreadStateException
public void start()
此线程要执行的任务,在此处定义代码。
public void run()
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
public static void sleep(long millis)
返回当前正在执行的线程对象的引用,引用即Thread线程的对象名
public static Thread currentThread()
步骤:
创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
调用线程对象的start()方法来启动线程。
代码:
1 public class TestRunnable implements Runnable { 2 @Override 3 public void run() { 4 for (int i = 0; i < 5; i++) { 5 System.out.println(i + "线程:" + Thread.currentThread().getName()); 6 } 7 } 8 }
1 public class Demo { 2 public static void main(String[] args) { 3 TestRunnable runnable = new TestRunnable(); 4 Thread thread = new Thread(runnable, "多线程"); 5 thread.start(); 6 for (int i = 0; i < 5; i++) { 7 System.out.println("主线程" + i); 8 } 9 } 10 }
结果:
主线程0
0线程:多线程
主线程1
1线程:多线程
主线程2
主线程3
2线程:多线程
主线程4
3线程:多线程
4线程:多线程
run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法而已。
如果一个类继承Thread,则不适合资源共享。
如果实现了Runable接口,则很容易的实现资源共享(可以传递相同的Runnable接口实现类对象调用同一个run方法做一样的事情,实现资源共享)
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。
匿名内部类,代表一个类或者一个接口的(干)子类对象,简化格式,前提要存在一个类或者一个接口
格式如下:
new 类名或接口名(){
重写方法,如果是普通类可以写也可以不重写,如果是抽象类或者是接口必须要重写抽象方法
}
使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。
1 public class Demo { 2 public static void main(String[] args) { 3 new Thread() { 4 @Override 5 public void run() { 6 System.out.println("开启多线程"); 7 } 8 }.start(); 9 System.out.println("主线程"); 10 11 } 12 }
使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法:
1 public class Demo { 2 public static void main(String[] args) { 3 new Thread(new Runnable() { 4 @Override 5 public void run() { 6 System.out.println("开启多线程"); 7 } 8 }).start(); 9 System.out.println("主线程"); 10 } 11 }
案例:模拟卖票,分四条执行路径(线程,窗口类模拟线程)
代码:
1 public class Ticket implements Runnable { 2 3 /** 4 * 定义票数 5 */ 6 private int ticket = 50; 7 8 @Override 9 public void run() { 10 //模拟窗口卖票服务 11 while (true) { 12 if (ticket > 0) {//有票 可以卖 13 //使用sleep模拟一下出票时间 14 try { 15 Thread.sleep(100); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 System.out.println(Thread.currentThread().getName() + "正在卖" + ticket-- + "张票"); 20 } else { 21 break; 22 } 23 } 24 } 25 } 26 27 class Demo { 28 public static void main(String[] args) { 29 //创建接口实现类对象 30 Ticket ticket = new Ticket(); 31 32 //创建卖票窗口对象 33 Thread windowsOne = new Thread(ticket, "窗口一"); 34 Thread windowsTwo = new Thread(ticket, "窗口二"); 35 Thread windowsThree = new Thread(ticket, "窗口三"); 36 37 //执行卖票 38 windowsOne.start(); 39 windowsTwo.start(); 40 windowsThree.start(); 41 } 42 }
结果:
窗口三正在卖48张票
窗口一正在卖49张票
窗口二正在卖50张票
窗口二正在卖47张票
窗口三正在卖47张票
...
窗口三正在卖3张票
窗口一正在卖2张票
窗口二正在卖3张票
窗口二正在卖0张票
窗口三正在卖-1张票
窗口一正在卖1张票
结果中有一部分这样现象:
窗口三正在卖3张票
窗口一正在卖2张票
窗口二正在卖3张票
窗口二正在卖0张票
窗口三正在卖-1张票
窗口一正在卖1张票
发现程序出现了两个问题:
相同的票数,比如3这张票被卖了两回。
不存在的票,比如0票与-1票,是不存在的。
这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
同步就可以让cpu在某段时间内只让一个线程进来做事情,其他线程不能进来。(理解为排队),同步技术包括,同步代码块,同步方法,Lock锁
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。
根据案例简述:
窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。
也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。
三种方式完成同步操作:
同步代码块。
同步方法。
锁机制。
同步代码块:synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){//锁对象可以是任意的,但是如果这个同步代码块被多次调用,要求锁对象是唯一。 需要同步操作的代码。 }
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
锁对象 可以是任意类型。
多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED进入阻塞状态)。
使用同步代码块解决代码:
1 public class Ticket implements Runnable { 2 3 /** 4 * 定义票数 5 */ 6 private int ticket = 50; 7 8 @Override 9 public void run() { 10 //模拟窗口卖票服务 11 while (true) { 12 synchronized (Ticket.class){ 13 if (ticket > 0) {//有票 可以卖 14 //使用sleep模拟一下出票时间 15 try { 16 Thread.sleep(100); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 System.out.println(Thread.currentThread().getName() + "正在卖" + ticket-- + "张票"); 21 } else { 22 break; 23 } 24 } 25 } 26 } 27 } 28 29 class Demo { 30 public static void main(String[] args) { 31 //创建接口实现类对象 32 Ticket ticket = new Ticket(); 33 34 //创建卖票窗口对象 35 Thread windowsOne = new Thread(ticket, "窗口一"); 36 Thread windowsTwo = new Thread(ticket, "窗口二"); 37 Thread windowsThree = new Thread(ticket, "窗口三"); 38 39 //执行卖票 40 windowsOne.start(); 41 windowsTwo.start(); 42 windowsThree.start(); 43 } 44 }
结果:
窗口二正在卖50张票
窗口一正在卖49张票
窗口三正在卖48张票
...
窗口一正在卖4张票
窗口二正在卖3张票
窗口一正在卖2张票
窗口三正在卖1张票
当使用了同步代码块后,上述的线程的安全问题就解决了。
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
public synchronized void method(){ 可能会产生线程安全问题的代码 }
同步锁是谁? 对于非static方法,同步锁就是this。 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
原文:https://www.cnblogs.com/cmmplb/p/11741236.html