注意: 多线程:多个线程并发执行。
java中创建线程的四种方式:
- 1. 继承 `Thread` 类 - 2. 实现 `Runnable` 接口 - 3. 实现 `Callable` 接口(与`Future`结合使用) - 4. `线程池`方式注意:
- 在java中,每次程序运行至少启动2个线程。一个是`main线程`,一个是`垃圾收集线程`。代码实现:
// 第一步:创建自定义线程类 class MyThread extends Thread { @Override public void run() { for (int i = 0; i<10; i++){ System.out.println("mythread线程正在执行:"+new Date().getTime()); } } } // 第二步:创建测试类 public class ThreadCreateDemo { public static void main(String[] args){ //1.创建自定义线程 MyThread thread = new MyThread(); thread.start(); //2.主线程循环打印 for (int i=0; i<10; i++){ System.out.println("main主线程正在执行:"+new Date().getTime()); } } }代码实现:
// 第一步:创建自定义类实现Runnable接口 class MyRunable implements Runnable { public void run() { for (int i=0; i<10; i++){ System.out.println("MyRunnable线程正在执行:"+new Date().getTime()); } } } // 第二步:创建测试类 public class ThreadCreateDemo { public static void main(String[] args){ //1.创建自定义线程 Thread thread = new Thread(new MyRunable()); thread.start(); //2.主线程循环打印 for (int i=0; i<10; i++){ System.out.println("main主线程正在执行:"+new Date().getTime()); } } }FutureTask介绍:
Callable需要使用FutureTask类帮助执行,FutureTask类结构如下:
Future接口:
判断任务是否完成:isDone()能够中断任务:cancel()能够获取任务执行结果:get()代码实现:
// 第一步:创建自定义类实现Callable接口 class MyCallable implements Callable<String> { @Override public String call() throws Exception { for (int i = 0; i < 10; i++) { Thread.sleep(100); System.out.println("MyCallable正在执行:" + new Date().getTime()); } return "MyCallable执行完毕!"; } } // 第二步:创建测试类 public class CallableDemo01 { public static void main(String[] args) { //1.使用Executors创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池 //2.通过线程池执行线程 for (int i = 0; i < 10; i++) { executorService.execute(new MyRunnable()); } //3.主线程循环打印 for (int i = 0; i < 10; i++) { System.out.println("main主线程正在执行:" + new Date().getTime()); } // 4. 关闭线程池 executorService.shutdown(); } } 案例说明:( Future接口) - 1. `isDone()`:可以判断是否该线程是否正在执行 - 2. `cancel( boolean )`:可以取消正在执行的线程。 - boolean = 'true'时 -- 线程`不再执行`,`get()`方法不可用。 - boolean = 'false'时 - 线程`继续执行`,`get()`方法不可用 - 3. `get()`:当线程正常执行结束后由返回值。 - 但如果线程被取消,则不能获取,并出现异常:`java.util.concurrent.CancellationException`线程池线类关系图
Executor接口:声明了execute(Runnable runnable)方法,执行任务代码
ExecutorService接口:继承Executor接口,声明方法:submit、invokeAll、invokeAny以及shutDown等
AbstractExecutorService抽象类: 实现ExecutorService接口,基本实现ExecutorService中声明的所有方法
ScheduledExecutorService接口:继承ExecutorService接口,声明定时执行任务方法
ThreadPoolExecutor类: 继承类AbstractExecutorService,实现execute、submit、shutdown、shutdownNow方法
ScheduledThreadPoolExecutor类:继承ThreadPoolExecutor类,实现ScheduledExecutorService接口并实现其中的方法
Executors类:提供快速创建线程池的方法
代码实现:
// 第一步:创建自定义类实现Runnable接口 class MyRunable implements Runnable { public void run() { for (int i=0; i<10; i++){ System.out.println("MyRunnable线程正在执行:"+new Date().getTime()); } } } // 第二步:创建测试类 public class ThreadCreateDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { //1.使用Executors创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池 //2.通过线程池执行线程 executorService.execute(new MyRunable()); //3.主线程循环打印 for (int i=0; i<10; i++){ System.out.println("main主线程正在执行:"+new Date().getTime()); } executorService.shutdown(); } }推荐实现接口。
Runnable和Callable接口比较:相同点:
- 1. 两者都是接口; - 2. 两者都可用来编写多线程程序; - 3. 两者都需要调用Thread.start()启动线程;不同点:
- 1. 实现Callable接口的线程能返回执行结果;而实现Runnable接口的线程不能返回结果; - 2. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的不允许抛异常; - 3. 实现Callable接口的线程可以调用Future.cancel取消执行 ,而实现Runnable接口的线程不能注意点
- Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现, 此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!问题演示:
public class TicketDemo { public static void main(String[] args){ Ticket ticket = new Ticket(); Thread thread1 = new Thread(ticket, "窗口1"); Thread thread2 = new Thread(ticket, "窗口2"); Thread thread3 = new Thread(ticket, "窗口3"); thread1.start(); thread2.start(); thread3.start(); } private static class Ticket implements Runnable { private int ticktNum = 100; public void run() { while(true){ if(ticktNum > 0){ //1.模拟出票时间 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //2.打印进程号和票号,票数减1 String name = Thread.currentThread().getName(); System.out.println("线程"+name+"售票:"+ticktNum--); } else { break; } } } } }运行结果: 出现票数为负数的情况、卖同一张票的情况
产生负数的原因
- 当`线程A`,在判断'ticktNum > 0'后进入后续代码,并获取得到票数'ticktNum'的数量为'1'。 此时`线程B`也通过了'ticktNum > 0'判断条件,也进入了后续的代码块。此时,`线程A`进行卖票操作之后, `线程B`再获取票数'ticktNum'的数量为'0',也进行了卖票操作。因此出现了票数为'-1'的现象。 卖相同票的原因: - 当`线程A`进入程序后,获取了票数'ticktNum'的数量为'10'; 与此同时`线程B`也进入了程序,也获取了票数'ticktNum'的数量为'10'; 然后`线程A`再进行卖票操作,之后`线程B`也进行卖票操作。因此卖出了同一张票总结:
多个线程在操作共享的数据;操作共享数据的线程代码有多条;多个线程对共享数据有写操作;要解决以上线程问题,只要在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
Java引入了7种线程同步机制:
- 1) `同步代码块`(synchronized) - 2) `同步方法`(synchronized) - 3) `同步锁`(ReenreantLock) - 4) 特殊域变量(volatile) - 5) 局部变量(ThreadLocal) - 6) 阻塞队列(LinkedBlockingQueue) - 7) 原子变量(Atomic*)java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
ReenreantLock public void lock(); //加同步锁。 public void unlock(); //释放同步锁。具体代码:
public void run() { lock.lock(); //加锁 // 需要同步的代码块... lock.unlock(); //放锁 }以下这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
互斥条件进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
不可剥夺条件进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
请求与保持条件进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
循环等待条件存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的资源被P0占有,如图所示。
循环等待 满足条件但无死循环网址:https://blog.csdn.net/First_Bal/article/details/107571935