Java多线程
1.线程的概念
-
程序 进程 线程
-
程序:指令集 静态概念
-
进程:操作系统调度程序 动态概念
-
线程:在进程内多条执行路径
-
2.线程和进程的区别
区 别 | 进程 | 线程 |
根 本 区 别 | 作为资源分配的单位 | 调度和执行的单位 |
开销 | 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销 | 线程可以看成轻量级的进程,同一类线程共享代码和数据空间,每个线程由独立的运行栈和程序技术器(PC),线程切换的开销小 |
所处环境 | 在操作系统中能同时运行多个任务(程序) | 在同一应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行的时候会为每个进程分配不同的内存区域 | 除了CPU之外,不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源 |
包含关系 | 没有线程的进程是可以被看做是单线程的,如果一个进程内拥有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的 | 线程是进程的一部分,所以线程有的时候被称为轻权进程或者轻量级进程 |
3.JAVA中实现多线程
-
在Java中负责线程功能的是java.lang.Thread这个类
-
可以通过常见Thread的实例来创建新的线程
-
每个线程都是通过某个特定Thread对象所对应的run()方法来完成其操作的,run()方法称为线程体
-
通过调用Thread对象的start()方法来启动一个线程
方法一:继承Thread类,重写run()方法,创建对象后调用start()方法
/**
* 模拟龟兔赛跑
* @author Matrix42
*
*/
public class Rabbit extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("兔子跑了"+i+"步");
}
}
}
class Tortoise extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("乌龟跑了"+i+"步");
}
}
}
public class RabbitApp {
public static void main(String[] args) {
//创建子类对象
Rabbit rabbit = new Rabbit();
//调用start()方法
rabbit.start();
Tortoise tortoise = new Tortoise();
tortoise.start();
}
}
方法二:实现Runnable接口
-
继承Thread类方式的缺点:如果我们的类已经从一个类继承,则无法再继承Thread类
-
通过Runnable接口实现多线程的优点:可以同时实现继承.实现Runnable接口方式要通用一些
-
避免单继承
-
方便共享资源 同一份资源 多个代理访问
-
静态代理:
/**
* 静态代理设计模式
* 1.真实角色
* 2.代理角色 持有真实角色的引用
* 3.二者实现相同的接口
* @author Matrix42
*
*/
public class StaticProxy {
public static void main(String[] args) {
//创建真实角色
You you = new You();
//创建代理角色+真实角色的引用
WeddingCompany weddingCompany = new WeddingCompany(you);
//执行任务
weddingCompany.marry();
}
}
//接口
interface Marry{
void marry();
}
//真实角色
class You implements Marry{
@Override
public void marry() {
System.out.println("你和嫦娥结婚了...");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry you;
public WeddingCompany(){
}
public WeddingCompany(Marry you) {
this.you = you;
}
private void Before(){
System.out.println("布置猪窝...");
}
private void After(){
System.out.println("闹玉兔...");
}
@Override
public void marry() {
Before();
you.marry();
After();
}
}
Demo:
/**
* 使用Runnable创建线程
* 1.类实现Runnable接口 重写run()方法
* 2.启动多线程 使用静态代理
* 1).创建真实角色
* 2).创建代理角色
* 3).调用.start()启动线程
* @author Matrix42
*
*/
public class Programmer implements Runnable{
@Override
public void run() {
for(int i=0;i<1000;i++){
System.out.println("一边敲helloworld....");
}
}
}
public class ProgrammerApp {
public static void main(String[] args) {
Programmer programmer = new Programmer();
Thread proxy = new Thread(programmer);
proxy.start();
for(int i=0;i<1000;i++){
System.out.println("一边聊QQ....");
}
}
}
方法三:通过Callable接口实现多线程(java.util.concurrent)[了解]
-
优点:可以获取返回值
Callable和Future接口
Callable类似Runnable接口,实现Callable接口的类和实现Runnable的类都是可以被其他线程执行的任务
Callable和Runnable有几点不同:
-
Callable规定的方法是call(),而Runnable规定的方法是run()
-
call()方法可以抛出异常,而run()方法不能抛出异常
-
Callable的任务执行后可返回值,运行Callable任务可拿到一个Future对象,而Runnable的任务是不能返回值的
-
Future表示异步计算的结果.它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果
通过Future对象可了解任务执行情况,可以取消任务的执行,还可以获取任务执行的结果
-
缺点:繁琐
-
思路:
-
创建Callable实现类+重写call
-
借助执行调度服务ExecutorService获取Future对象
ExecutorService ser = Executors.newFixedThreadPool(2);
Future result = ser.submit(实现类对象);
-
获取值result.get();
-
停止服务ser.shutdownNow();
-
Demo:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 使用Callable创建线程
* @author Matrix42
*
*/
public class Call {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//创建线程
ExecutorService ser = Executors.newFixedThreadPool(2);
Race tortoise = new Race("乌龟",1000);
Race rabbit = new Race("兔子",500);
//获取值
Future<Integer> result1 = ser.submit(tortoise);
Future<Integer> result2 = ser.submit(rabbit);
Thread.sleep(2000);
tortoise.setFlag(false);
rabbit.setFlag(false);
int num1 = result1.get();
int num2 = result2.get();
System.out.println("乌龟跑了--->"+num1+"步");
System.out.println("兔子跑了--->"+num2+"步");
//停止线程
ser.shutdownNow();
}
}
//尖括号里的类型表示返回值类型 call()的返回值与它相同
class Race implements Callable<Integer>{
private String name;
private long time;
private boolean flag = true;
private int step = 0;//步
Race(){
}
Race(String name){
this.name = name;
}
Race(String name,long time){
this.name = name;
this.time = time;
}
@Override
public Integer call() throws Exception {
while(flag){
Thread.sleep(time);
step++;
}
return step;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getStep() {
return step;
}
public void setStep(int step) {
this.step = step;
}
}
每个方法隐式的向外声明RuntimeException
4.线程的状态
<---阻塞状态<---
| |
创建---start()--->就绪状态<---->运行状态----->终止
-
新生状态
用new关键字和Thread类或其子类建立一个线程后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)
-
就绪状态
处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列,等待系统为其分配CPU.等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为"CPU调度",一旦获得CPU,线程就进入运行状态并自动调用自己的run方法
-
运行状态
在运行状态的线程执行自己的run方法中的代码,知道调用其他方法而终止或等待某资源而阻塞或完成任务而死亡,如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态
-
阻塞状态
处于运行状态的线程在某些情况下,如执行了sleep方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态,在阻塞状态的线程不能进入就绪队列.只有当引起阻塞的原因消除时,如sleep时间已到,或等待的I/O设备空闲下来,线程变转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续执行.
-
死亡状态
死亡状态是线程生命周期中的最后一个阶段.线程死亡的原因有两个.一个是正常运行的线程完成了它全部工作;另一个是线程被强制性的终止,如通过执行stop或destory方法来终止一个线程[不推荐使用这两个方法,前者会产生异常,后者是强行终止,不会释放锁]
5.停止线程
-
自然终止:线程体正常执行完毕
-
外部干涉:
-
线程类中定义线程体使用的标识
-
线程体使用该标识
-
提供对外的方法改变该标识
-
外部根据条件调用该方法即可
-
Demo:
public class Demo {
public static void main(String[] args) {
Study s = new Study();
new Thread(s).start();
for(int i=0;i<100;i++){
if(i==50){
s.stop();
}
System.out.println("main...."+i);
}
}
}
class Study implements Runnable {
private boolean flag = true;
@Override
public void run() {
while(flag){
System.out.println("study thread...");
}
}
public void stop() {
this.flag = false;
}
}
6.线程阻塞
- join:把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程
Demo:
public class JoinDemo extends Thread{
public static void main(String[] args) throws InterruptedException {
JoinDemo jDemo = new JoinDemo();
jDemo.start();
for(int i=0;i<1000;i++){
if(50==i){
jDemo.join(); //main阻塞
}
System.out.println("main..."+i);
}
}
@Override
public void run() {
for(int i=0;i<1000;i++){
System.out.println("Thread....."+i);
}
}
}
- yield:暂停当前线程(static方法)
public class YieldDemo extends Thread{
public static void main(String[] args) throws InterruptedException {
JoinDemo jDemo = new JoinDemo();
jDemo.start();
for(int i=0;i<1000;i++){
if(i%20==0){
Thread.yield();//暂停本线程
}
System.out.println("main..."+i);
}
}
@Override
public void run() {
for(int i=0;i<1000;i++){
System.out.println("yield....."+i);
}
}
}
-
sleep:强制当前正在执行的线程休眠(静态方法),不释放锁
用途:
-
与时间相关,倒计时
-
网络延时
-
Demo 01:
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 1.倒数10个数
* 2.倒计时
* @author Matrix42
*
*/
public class SleepDemo {
public static void main(String[] args) throws InterruptedException {
test02();
}
public static void test01() throws InterruptedException{
int num = 10;
while(true){
System.out.println(num--);
Thread.sleep(1000); //暂停1秒
if(num<=0){
break;
}
}
}
public static void test02() throws InterruptedException{
Date endTime = new Date(System.currentTimeMillis()+10*1000);
long end = endTime.getTime();
while(true){
System.out.println(new SimpleDateFormat("mm:ss").format(endTime));
endTime = new Date(endTime.getTime()-1000);
Thread.sleep(1000);
if(end-10000>endTime.getTime()){
break;
}
}
}
}
Demo 02:
/**
* 模拟网络延时 线程不安全
* @author Matrix42
*
*/
public class SleepDemo02 {
public static void main(String[] args) {
Web12306 web = new Web12306();
Thread t1 = new Thread(web,"路人甲");
Thread t2 = new Thread(web,"黄牛乙");
Thread t3 = new Thread(web,"攻城狮");
t1.start();
t2.start();
t3.start();
}
}
class Web12306 implements Runnable{
private int num = 50;
@Override
public void run() {
while(true){
if(num<=0){
break;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
}
}
会产生对同一个资源的并发问题:
黄牛乙抢到了3
攻城狮抢到了2
黄牛乙抢到了1
路人甲抢到了0
攻城狮抢到了-1
7.线程基本信息与优先级
方法 | 功能 |
---|---|
isAlive() | 判断线程是否还"活"着,即线程是否还未终止 |
getPriority() | 获得线程的优先级数值 |
setPriority() | 设置线程的优先级数值 |
setName() | 给线程一个名字 |
getName() | 取得线程的名字 |
currentThread() | 取得当前正在运行的线程对象也就是取得自己本身 |
Demo:
public class MyThread implements Runnable{
private boolean flag = true;
private int num = 0;
@Override
public void run() {
while(flag){
System.out.println(Thread.currentThread().getName()+"-->"+num++);
}
}
public void stop(){
this.flag = false;
}
}
public class InfoDemo {
public static void main(String[] args) throws InterruptedException {
MyThread it = new MyThread();
Thread proxy = new Thread(it,"挨踢"); //如果不取名则自动编号Thread0....
proxy.setName("IT");
System.out.println(proxy.getName());
System.out.println(Thread.currentThread().getName());
proxy.start();
System.out.println(proxy.isAlive());
Thread.sleep(200);
it.stop(); //不会马上停止
Thread.sleep(100);
System.out.println(proxy.isAlive());
}
}
/**
* 优先级不代表绝对的先后顺序,而是被执行的概率
* MAX_PRIORITY 10
* NORM_PRIORITY 5 默认
* MIN_PRIORITY 1
* @author Matrix42
*
*/
public class InfoDemo02 {
public static void main(String[] args) throws InterruptedException {
MyThread it = new MyThread();
Thread p1 = new Thread(it,"挨踢");
MyThread it2 = new MyThread();
Thread p2 = new Thread(it,"挨踢2");
p1.setPriority(Thread.MAX_PRIORITY);
p2.setPriority(Thread.MIN_PRIORITY);
p1.start();
p2.start();
Thread.sleep(1000);
it.stop();
it2.stop();
}
}
8.线程同步与锁定
-
由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问
-
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种方法:synchronized方法和synchronized块,synchronized块锁定的只能是引用类型|this|类.class
Demo:
/**
* 模拟网络延时 线程安全但速度慢
* @author Matrix42
*
*/
public class SleepDemo02 {
public static void main(String[] args) {
Web12306 web = new Web12306();
Thread t1 = new Thread(web,"路人甲");
Thread t2 = new Thread(web,"黄牛乙");
Thread t3 = new Thread(web,"攻城狮");
t1.start();
t2.start();
t3.start();
}
}
class Web12306 implements Runnable{
private int num = 50;
private boolean flag = true;
@Override
public void run() {
while(flag){
test1();
}
}
public synchronized void test1(){
if(num<=0){
flag = false;
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
}
synchronized块:
public void test1(){
synchronized(this){
if(num<=0){
flag = false;
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
}
单例设计模式:
public class SynDemo {
public static void main(String[] args) {
new JvmThread(100).start();
new JvmThread(500).start();
/*
* 使用getInstance1
* Thread-0-->创建:com.lorinda.thread.Jvm@854a9ed
* Thread-1-->创建:com.lorinda.thread.Jvm@29e0f336
* 使用getInstance2 效率不高
* Thread-0-->创建:com.lorinda.thread.Jvm@29e0f336
* Thread-1-->创建:com.lorinda.thread.Jvm@29e0f336
* * 使用getInstance3 效率不高
* Thread-1-->创建:com.lorinda.thread.Jvm@29568226
* Thread-0-->创建:com.lorinda.thread.Jvm@29568226
* */
}
}
class JvmThread extends Thread{
private long time;
public JvmThread(){}
public JvmThread(long time){
this.time = time;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->创建:"+Jvm.getInstance(time));
}
}
/**
* 单例设计模式
* 确保一个类只有一个对象
* 懒汉式
* 1.构造器私有化,避免外部直接创建对象
* 2.声明一个私有化的静态变量
* 3.创建一个对外的公共的静态方法访问该变量,如果变量没有对象,创建该对象
* @author Matrix42
*
*/
class Jvm{
//声明一个私有的静态变量
private static Jvm instance = null;
//构造器私有化,避免外部直接创建对象
private Jvm(){
}
//创建一个对外的公共的静态方法访问该变量,如果变量没有对象,创建该对象
public static Jvm getInstance1(long time){
if(null==instance){
try {
Thread.sleep(time);//延时,放大错误发生的概率
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
}
return instance;
}
//效率不高
public static synchronized Jvm getInstance2(long time){
if(null==instance){
try {
Thread.sleep(time);//延时,放大错误发生的概率
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
}
return instance;
}
//效率不高 存在对象也需要等待
public static synchronized Jvm getInstance3(long time){
synchronized (Jvm.class) {
if(null==instance){
try {
Thread.sleep(time);//延时,放大错误发生的概率
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
}
}
return instance;
}
//提高效率 已经有对象了就不用等了
//double checking
public static synchronized Jvm getInstance4(long time){
if(null==instance){ //提高效率
synchronized (Jvm.class) {
if(null==instance){ //安全
try {
Thread.sleep(time);//延时,放大错误发生的概率
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
}
}
}
return instance;
}
}
饿汉式:
/**
* 饿汉式
* 1.构造器私有化
* 2.声明私有的静态属性,同时创建该对象
* 3.对外提供访问属性的静态方法
* @author Matrix42
*
*/
class Jvm2{
private static Jvm2 instance = new Jvm2();
private Jvm2(){}
public static Jvm2 getInstance(){
return instance;
}
}
优化的饿汉式:
/**
* 类在使用的时候才加载,延缓了加载时间
* @author Matrix42
*
*/
class Jvm3{
private static class JVMholder{
private static Jvm3 instance = new Jvm3();
}
private Jvm3(){}
public static Jvm3 getInstance(){
return JVMholder.instance;
}
}
Runtime类就用的单例模式(饿汉式)
9.死锁
过多的同步容易产生死锁,比如一手给钱一手给货,你不给钱我就不给货
public class SynDemo02 {
public static void main(String[] args) {
Object g = new Object();
Object m = new Object();
Test t1 = new Test(g,m);
Test2 t2 = new Test2(g, m);
Thread thread = new Thread(t1);
Thread thread2 = new Thread(t2);
thread.start();
thread2.start();
}
}
class Test implements Runnable{
Object goods;
Object money;
public Test(Object goods, Object money) {
super();
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while(true){
test();
}
}
public void test(){
synchronized (goods) { //锁住goods还要money
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (money) {
}
}
System.out.println("一手给钱..");
}
}
class Test2 implements Runnable{
Object goods;
Object money;
public Test2(Object goods, Object money) {
super();
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while(true){
test();
}
}
public void test(){
synchronized (money) { //锁住money还要goods
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (goods) {
}
}
System.out.println("一手给货..");
}
}
解决同步的方法:生产者消费者模式
10.生产者消费者模式_信号灯法
-
生产者消费者问题(Producer-consumer problem),也称为有限缓存问题(Bounded-buffer problem),是一个多线程同步问题的经典案例.该问题描述了两个共享固定大小缓冲区的线程---即所谓的"生产者"和消费者"在实际运行时会发生的问题.生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程.与此同时,消费者也在缓冲区消耗这些数据.该问题的关键就是要保证生产者不会再缓冲区满时加入数据,消费者也不会在缓冲区空时消耗数据
-
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据之后,再唤醒消费者.通常常用的方法有信号灯法,管程法等.如果解决方法不够完善,则容易出现死锁的情况.出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己
Demo:
/**
* 一个场景,共同的资源
* 生产者消费者模式信号灯法
* wait()会释放锁,sleep()不释放锁
* notify()/notifyAll()唤醒
*与synchronized一起使用
*/
public class Movie {
private String pic;
//flag-->true 生产者生产,消费者等待,生产完成后通知消费
//flag-->false 消费者消费,生产者等待,消费完后通知生产
private boolean flag = true;
/**
* 播放
*/
public synchronized void play(String pic){
if(!flag){ //生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//开始生产
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产了:"+pic);
//生产完毕
this.pic = pic;
//通知消费
this.notify();
//生产者停下
this.flag=false;
}
public synchronized void watch(){
if(flag){ //消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//开始消费
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//消费完毕
System.out.println("消费了:"+pic);
//通知生产
this.notify();
//停止消费
this.flag=true;
}
}
/**
* 生产者
* @author Matrix42
*
*/
public class Player implements Runnable {
private Movie m;
public Player(Movie m) {
super();
this.m = m;
}
public void run() {
for(int i=0;i<20;i++){
if(i%2==0){
m.play("左青龙");
}else {
m.play("右白虎");
}
}
}
}
public class Watcher implements Runnable {
private Movie m;
public Watcher(Movie m) {
super();
this.m = m;
}
public void run() {
for(int i=0;i<20;i++){
m.watch();
}
}
}
public class App {
public static void main(String[] args) {
//共同的资源
Movie m = new Movie();
//多线程
Player p = new Player(m);
Watcher w = new Watcher(m);
new Thread(p).start();
new Thread(w).start();
}
}
11.任务调度
-
Timer定时器类
-
TimerTask任务类
-
通过java timer timertask:(spring的任务调度就是通过他们来实现的)在这种方式中,Timer类实现的是类似闹钟的功能,也就是定时或者每隔一定时间触发一次线程.其实,Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其它线程的.二TimerTask类是一个抽象类,该类实现了Runnable接口,所以按照前面的介绍,该类具备多线程的能力.
-
在这种实现方式中,通过继承TimerTask使该类获得多线程的能力,将需要多线程执行的代码写在run方法的内部,然后通过Timer类启动线程的执行.
-
在实际使用时,一个Timer可以启动任意多个TimerTask实现的线程,但是多个线程之间会存在阻塞.所以如果多个线程之间需要完全独立运行的话,最好是一个Timer启动一个TimerTask
import java.util.Timer;
import java.util.TimerTask;
/**
* schedule(TimerTask task,Date time) 定时执行一次
* schedule(TimerTask task,Date time,long period) 每隔period执行一次
* 了解,可以看看第三方框架quartz
* @author Matrix42
*
*/
public class TimeDemo1 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("so easy...");
}
}, 1000,200);
}
}
12.总结
-
一,创建线程
-
1.继承Thread
-
实现Runnable
-
实现Callable(了解)
-
-
二,线程的状态
-
- 新生-->start-->就绪-->运行-->阻塞-->终止
-
2.终止线程
-
3,阻塞 join yield sleep
-
-
三,线程的信息
-
1.Thread.currentThread
-
2.获取名称 设置名称 设置优先级 判断状态
-
-
四,同步:对同一份资源
synchronized(引用类型变量|this|类.class){}
修饰符 synchronized 方法名{
}过多的同步可能会造成死锁
-
五,生产者消费者模式
-
六,任务调度
后期:juc,quartz 自学