一个线程是一个程序内部的顺序控制流
线程和进程
多进程:在操作系统中,能同时运行多个任务(程序)
多线程:在同一应用程序中,有多个顺序流同时执行
线程的概念模型
线程体
构造线程的两个方法
定义一个线程类,它继承类Thread并重写其中的方法run();
提供一个实现接口Runnable的类作为线程的目标对象,在初始化一个Thread类或者Thread子类的线程对象时,把目标对象传递给这个线程实例,由该目标对象提供线程体run()。
继承Thread类–创建多线程的方法之一
创建线程的实例
public class FactorialThread extends Thread{ //继承Thread类
private int num;
public FactorialThread(int num)
{
this.num = num;
}
public void run() //重写run方法
{
int i=num;
int result=1;
System.out.println("New Thread Started!");
while(i>0)
{
result*=i;
i--;
}
System.out.println("The Factorial of "+num+" is "+result);
System.out.println("New Thread Ends");
}
public static void main(String[] args) {
FactorialThread f = new FactorialThread(3);
f.start(); //通过调用start方法启动线程
System.out.println("main thread ends!");
}
}
输出结果为
main thread starts
main thread ends!
New Thread Started!
The Factorial of 3 is 6
New Thread Ends
我们先修改前面的程序,让主线程main休眠一毫秒。
public class FactorialThread extends Thread{
private int num;
public FactorialThread(int num)
{
this.num = num;
}
public void run()
{
int i=num;
int result=1;
System.out.println("New Thread Started!");
while(i>0)
{
result*=i;
i--;
}
System.out.println("The Factorial of "+num+" is "+result);
System.out.println("New Thread Ends");
}
public static void main(String[] args) {
System.out.println("main thread starts");
FactorialThread f = new FactorialThread(10);
f.start();
try
{
Thread.sleep(1);
}
catch (Exception e) {}
System.out.println("main Thread ends");
}
}
输出结果为
main thread starts
New Thread Started!
The Factorial of 10 is 3628800
New Thread Ends
main Thread ends
可以看到,一个线程的休眠,使得其他线程得以运行
可以试着运行这个程序,观察结果
public class ThreadTester extends Thread{
private int SleepTime;
public ThreadTester(String name)
{
super(name);
SleepTime = (int)(Math.random()*6000); //取一个随机随眠时间
}
public void run()
{
try {
System.out.println(
getName()+"Going to sleep for "+SleepTime);
Thread.sleep(SleepTime);
} catch (InterruptedException e) {}
System.out.println(getName()+"finished");
}
}
//另一个类
public class ThreadSleepTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadTester thread1 = new ThreadTester("thread1");
ThreadTester thread2 = new ThreadTester("thread2");
ThreadTester thread3 = new ThreadTester("thread3");
System.out.println("Starting threads");
thread1.start();
thread2.start();
thread3.start();
System.out.println("Threads Started, main ends\n");
}
}
Runnable接口
修改前面的代码
public class FactorialThread implements Runnable{ //实现Runnable接口
private int num;
public FactorialThread(int num)
{
this.num = num;
}
public void run()
{
int i=num;
int result=1;
System.out.println("New Thread Started!");
while(i>0)
{
result*=i;
i--;
}
System.out.println("The Factorial of "+num+" is "+result);
System.out.println("New Thread Ends");
}
public static void main(String[] args) {
System.out.println("main thread starts");
FactorialThread f = new FactorialThread(10);
new Thread(f).start(); //以实现Runnable的对象为参数建立新的线程
try
{
Thread.sleep(1);
}
catch (Exception e) {}
System.out.println("main Thread ends");
}
}
同样的,我们修改前面另外一个程序的代码
public class ThreadTester implements Runnable{
private int SleepTime;
public ThreadTester()
{
SleepTime = (int)(Math.random()*6000);
}
public void run()
{
try {
System.out.println(
Thread.currentThread().getName()+"Going to sleep for "+SleepTime);
Thread.sleep(SleepTime);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName()+"finished");
}
}
测试类
public class ThreadSleepTester {
public static void main(String[] args) {
ThreadTester thread1 = new ThreadTester();
ThreadTester thread2 = new ThreadTester();
ThreadTester thread3 = new ThreadTester();
System.out.println("Starting threads");
new Thread(thread1,"Thread1").start();
new Thread(thread2,"Thread2").start();
new Thread(thread3,"Thread3").start();
System.out.println("Threads Started, main ends\n");
}
}
我们看一下前面的代码,如果我们修改成这样会出现什么?
public class ThreadSleepTester {
public static void main(String[] args) {
ThreadTester thread1 = new ThreadTester();
System.out.println("Starting threads");
new Thread(thread1,"Thread1").start();
new Thread(thread1,"Thread1").start();
new Thread(thread1,"Thread1").start();
System.out.println("Threads Started, main ends\n");
}
}
输出结果
Starting threads
Threads Started, main ends
Thread1Going to sleep for 3389
Thread1Going to sleep for 3389
Thread1Going to sleep for 3389
Thread1finished
Thread1finished
Thread1finished
可以看到,通过Runnable来构造我们的线程体,这些线程是数据共享的
我们看一个数据共享的例子
使用线程模拟三个售票机出售200张票
public class SellTickets implements Runnable{
private int tickets=200;
public void run()
{
while(tickets>0)
{
System.out.println(
Thread.currentThread().getName()+
" is selling ticket "+tickets--);
}
}
}
测试类
public class SellTicketsTester {
public static void main(String[] args) {
SellTickets t = new SellTickets();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
输出结果
Thread-1 is selling ticket 200
Thread-1 is selling ticket 197
Thread-2 is selling ticket 198
Thread-2 is selling ticket 195
Thread-2 is selling ticket 194
...
有时线程之间彼此不独立、需要同步
关于线程互斥可以看这里的内容,操作系统进程锁机制
用两个线程模拟存票、售票过程
我们实现这个代码的关键之处就是两个线程对这个票的修改操作一定不能同时进行,不然就乱套了
测试类
public class ProducerAndConsumer {
public static void main(String[] args) {
Tickets t = new Tickets(10);
new Consumer(t).start();
new Producer(t).start();
}
}
票类
public class Tickets {
int number = 0; //票号
int size; //票的最高数量
boolean available = false; //表示是否有票可出售
public Tickets(int size) //构造函数初始化
{
this.size = size;
}
}
生产者,也就是存票机
public class Producer extends Thread{
Tickets t = null;
public Producer(Tickets t)
{
this.t = t;
}
public void run()
{
while(t.number < t.size) //当票号小于总数,开始存票
{
System.out.println("Producer puts ticket "
+ (++t.number));
t.available = true;
}
}
}
消费者,也就是售票机
public class Consumer extends Thread{
Tickets t = null;
int i=0;
public Consumer(Tickets t)
{
this.t = t;
}
public void run()
{
while(i
输出结果
Producer puts ticket 1
Consumer buys tickets 1
Producer puts ticket 2
Consumer buys tickets 2
Producer puts ticket 3
Consumer buys tickets 3
Producer puts ticket 4
Consumer buys tickets 4
Producer puts ticket 5
Consumer buys tickets 5
Producer puts ticket 6
Consumer buys tickets 6
Producer puts ticket 7
Consumer buys tickets 7
Producer puts ticket 8
Producer puts ticket 9
Consumer buys tickets 8
Consumer buys tickets 9
Producer puts ticket 10
Consumer buys tickets 10
以上结果是不唯一的,有各种各样的可能
如果我们修改一下代码
if(i==t.number)
{
try{Thread.sleep(1);}
catch(InterruptedException exception) {}
t.available = false;
}
看一下输出结果
直接就死循环了
为什么?
if(i==t.number)
{
try{Thread.sleep(1);}
catch(InterruptedException exception) {}
t.available = false;
}
当售票机在将t.available置成false之前,休眠了一毫秒,导致生产者进程先执行完,然后没有线程再将aviliable置成true,因此消费者线程就陷入了死循环
前面的代码其实是有问题的
我们看一个输出结果
Producer puts ticket 1
Producer puts ticket 2
Consumer buys tickets 1
Producer puts ticket 3
Consumer buys tickets 2
Producer puts ticket 4
Consumer buys tickets 3
Producer puts ticket 5
Consumer buys tickets 4
Consumer buys tickets 5
Consumer buys tickets 6
Producer puts ticket 6
Producer puts ticket 7
Consumer buys tickets 7
Consumer buys tickets 8
Producer puts ticket 8
Producer puts ticket 9
Producer puts ticket 10
Consumer buys tickets 9
Consumer buys tickets 10
可以看到,生产者还没生产出6号票,但是消费者却消费了6号票,为什么会这样?
其实这就是生产者消费者的对Tickets这个类的修改没有实现互斥,两个线程同时修改Tickets肯定会出错,可以参照我前面的那篇文章
如何实现互斥与协作?
synchronized – 线程同步关键字,实现互斥
用于指定需要同步的代码段或方法,也就是监视区
可实现与一个锁的交互。例如
synchronized的功能是:首先判断对象的锁是否存在,如果在就获得锁,然后就可以执行紧随其后的代码段;如果对象的锁不在(已被其他的线程拿走),就进入等待状态,直到获得锁
当被synchronized限定的代码段执行完,就释放锁
现在,我们利用锁机制来重写前面的代码,让存票过程和售票过程互斥,即不能同时进行
售票过程和存票过程都是对Tickets对象的修改,而一个对象只有一个锁,我们就利用多进程对锁的争夺实现线程的互斥
代码实现
Consumer类
public class Consumer extends Thread{
Tickets t = null;
int i=0;
public Consumer(Tickets t)
{
this.t = t;
}
public void run()
{
while(i
Producer类
public class Producer extends Thread{
Tickets t = null;
public Producer(Tickets t)
{
this.t = t;
}
public void run()
{
while(t.number < t.size)
{
synchronized(t)//获得锁
{
System.out.println("Producer puts ticket "
+ (++t.number));
t.available = true;
}//释放锁
}
System.out.println("Producer ends");
}
}
输出结果:
Producer puts ticket 1
Consumer buys tickets 1
Producer puts ticket 2
Producer puts ticket 3
Producer puts ticket 4
Producer puts ticket 5
Consumer buys tickets 2
Consumer buys tickets 3
Consumer buys tickets 4
Consumer buys tickets 5
Producer puts ticket 6
Producer puts ticket 7
Producer puts ticket 8
Producer puts ticket 9
Producer puts ticket 10
Producer ends
Consumer buys tickets 6
Consumer buys tickets 7
Consumer buys tickets 8
Consumer buys tickets 9
Consumer buys tickets 10
Consumer ends
可以多运行几次测试一下,不会再出现消费者“超前消费”的现象
**说明:**当线程通过synchronized得到不到对象的锁时,就会被放到对应的等待线程池里边,后面的线程调度算法就是研究应该从线程池里取出拿一个线程来执行
可以通过synchronized关键字定义整个方法在同步控制下执行
修改一下上面的代码
public class Tickets {
int number = 0;
int size;
int i=0;
boolean available = false;
public Tickets(int size)
{
this.size = size;
}
public synchronized void put()
{
System.out.println("Producer puts ticket "
+ (++number));
available = true;
}
public synchronized void buy()
{
if(available==true && i<=number)
System.out.println("Consumer buys ticket"+(++i));
if(i==number)
available=false;
}
}
public class Producer extends Thread{
Tickets t = null;
public Producer(Tickets t)
{
this.t = t;
}
public void run()
{
while(t.number
public class Consumer extends Thread{
Tickets t = null;
int i=0;
public Consumer(Tickets t)
{
this.t = t;
}
public void run()
{
while(t.i
同步与锁的要点
线程的等待–wait()方法:wait方法使线程进入等待状态并释放对象X
的锁,这时它需要其它线程在对象X
上调用notify
和notifyAll
方法来唤醒它,这一过程体现了线程之间的沟通
线程的唤醒–notify()和notifyAll()方法
notify()随机唤醒一个等待的线程,本线程继续执行
notifyAll()唤醒所有等待的线程,本线程继续执行
我们修改一下前面的售票机存票机工作原理
现在要求存票机存一张售票机就卖一张
代码实现:
public class Tickets {
int number = 0;
int size;
int i=0;
boolean available = false;
public Tickets(int size)
{
this.size = size;
}
public synchronized void put()
{
if(available==true)
try {wait();} //如果票还没卖出去,就等待
catch (InterruptedException e) {}
System.out.println("Producer puts ticket "
+ (++number));
available = true;
notify(); //唤醒线程
}
public synchronized void buy()
{
if(available==false)
try {wait();} //如果没票就等待
catch (InterruptedException e) {}
System.out.println("Consumer buys tickets "+number);
available=false;
notify(); //唤醒线程
if(number==size) //给个退出条件
number=size+1;
}
}
public class Producer extends Thread{
Tickets t = null;
public Producer(Tickets t)
{
this.t = t;
}
public void run()
{
while(t.number
public class Consumer extends Thread{
Tickets t = null;
public Consumer(Tickets t)
{
this.t = t;
}
public void run()
{
while(t.number
测试类跟前面的一样,这里就不重新贴上去了
后台线程
创建一个无限循环的后台线程,验证主线程结束后,程序即结束
public class Ex8_10 {
public static void main(String[] args) {
ThreadTest t = new ThreadTest();
t.setDaemon(true);
t.start();
}
}
public class ThreadTest extends Thread{
public void run()
{
while(true)
{}
}
}
如果注释掉t.setDaemon(true)
,那么程序将永不停止
简要说明一下死锁问题,有关死锁的更详细内容可看这篇博客进程同步与信号量以及死锁处理
死锁表示一个线程占有着一个资源,但是它需要得到另外一个资源才能完成工作并释放资源,这时,如果它需要得到的另外一个资源在另外一个线程上,同时这个线程跟它处于一样的情况,当多个这样的线程围成一个环,就出现了死锁问题,谁也不释放资源同时谁也得不到资源
简单的死锁避免方法
假设有这样一个游戏,三个人站在三角形的三个顶点,三条边上有三个球,每个人必须先拿到左边的球才能再拿到右手边的球,两手都有球之后,才能把两个球都放下
如图所示:
有没有发现,跟前面的死锁问题很像,如果每个人左手都拿着球,那每个人都没办法拿到右手边的球,那他们永远没办法把球放下,这就是死锁问题,我们用代码实现一下这个过程
public class Ex8_11 {
public static void main(String[] args) {
Balls ball = new Balls();
Player0 p0 = new Player0(ball);
Player1 p1 = new Player1(ball);
Player2 p2 = new Player2(ball);
p0.start();
p1.start();
p2.start();
}
}
class Balls{
boolean flag0 = false;
boolean flag1 = false;
boolean flag2 = false;
}
class Player0 extends Thread{
private Balls ball;
public Player0(Balls b)
{
this.ball = b;
}
public void run()
{
while(true)
{
while(ball.flag1 == true) {};
//如果左手边球有人持着,就进入死循环等待
ball.flag1 = true;
//获得左手球
while(ball.flag0 == true) {};
//右手球有人持着,就进入死循环等待
if(ball.flag1==true && ball.flag0==false)
{
ball.flag0 = true;
//持右手球
System.out.println("Player0 has got two balls!");
ball.flag1 = false;
ball.flag0 = false;
//释放左右手球
try {sleep(1);}
catch(Exception e){}
//休眠1毫秒
}
}
}
}
class Player1 extends Thread{
private Balls ball;
public Player1(Balls b)
{
this.ball = b;
}
public void run()
{
while(true)
{
while(ball.flag0 == true) {};
ball.flag0 = true;
while(ball.flag2 == true) {};
if(ball.flag0==true && ball.flag2==false)
{
ball.flag2 = true;
System.out.println("Player1 has got two balls!");
ball.flag0 = false;
ball.flag2 = false;
try {sleep(1);}
catch(Exception e){}
}
}
}
}
class Player2 extends Thread{
private Balls ball;
public Player2(Balls b)
{
this.ball = b;
}
public void run()
{
while(true)
{
while(ball.flag2 == true) {};
ball.flag2 = true;
while(ball.flag1 == true) {};
if(ball.flag2==true && ball.flag1==false)
{
ball.flag1 = true;
System.out.println("Player3 has got two balls!");
ball.flag2 = false;
ball.flag1 = false;
try {sleep(1);}
catch(Exception e){}
}
}
}
}
运行一段时间之后,你就会发现进入了程序不会再输出任何信息,一个每一个玩家都没办法得到两个球,进入死锁状态
如果想避免死锁,可以修改规则,比如说,如果手持左手球时,右手球不能得到,就将左手球放下
线程调度的要点如下
线程的调度就是CPU维护线程的队列,应该将哪个线程放在队列前面,哪个线程放在队列后面
Java虚拟机使用简单的优先级方法,优先级高的线程先执行
关于线程优先级,一般线程优先级初始化为5,在1-10之间
一个线程运行过程中创建的另一个线程,被创建的线程优先级与创建它的线程相同
线程创建了,可以通过setPriority(int priority)
方法修改线程的优先级
优先级高的线程比优先级低的线程先执行
相同优先级的线程,Java的处理方式为随机选择一个线程来执行
线程的优先级应该是基于效率来考虑的,而不是用来保证算法的正确性
一个正在运行的线程只有在以下情况才会停止
wait()
,sleep
,yield()
方法关于操作系统线程调度可以看这篇文章CPU调度策略以及linux0.11中的实现
我们用代码来观察以下Java线程调度
public class Ex8_13 {
public static void main(String[] args) {
TestThread[] runners = new TestThread[2];
for(int i=0; i<2; i++)
runners[i] = new TestThread(i);
runners[0].setPriority(2); //设置优先级
runners[1].setPriority(3);
for(int i=0; i<2; i++)
runners[i].start();
}
}
class TestThread extends Thread{
private int tick=1;
private int num;
public TestThread(int i) {this.num=i;}
public void run()
{
while(tick<400000)
{
tick++;
if((tick%50000)==0)
{
System.out.println("Thread#"+num+",tick="+tick);
yield(); //让出CPU给同优先级的进程执行
}
}
}
}
如果你的电脑是单CPU的,那么你就会发现,只有等线程1执行完之后,线程0才能执行,但是如果你电脑是多CPU的,这代码就没有多大的参考价值了
但是我们依然可以得到下列几点
一个线程调用yield()方法,只有优先级相同跟这个线程相同才会被调度
但是一个线程如果调用sleep,wait等方法,则所有线程都可以来争夺CPU,而Java会优先让优先级高的线程执行
线程安全不是指这个线程是否安全,而是指一个对象在多线程的情况下,不使用同步等措施,各个线程调用这个对象都能得到正确的结果,那么这个对象就是线程安全的
不可边:既然这个对象不能被修改,那它肯定就是线程安全了
绝对线程安全
相对线程安全
我们看一个Vector的例子
import java.util.Vector;
public class VectorSafe {
private static Vectorvector=new Vector();
public static void main(String[] args) {
while(true)
{
for(int i=0; i<10; i++)
vector.add(i);
Thread removeThread = new Thread(new Runnable(){
public void run()
{
for(int i=0; i20);
//保证程序中的线程数量不超过20
}
}
}
在运行的时候,你会发现,有时候会突然闪过一个错误,数组越界的错误,因为removeThread
和printThread
两个线程同时对vector
对象操作,有可能一个线程刚删除,另外一个线程就去访问被删除的元素,所以就造成了数组越界,因此Vector并不是绝对线程安全,我们可以给它加上一些同步使它变成线程安全
import java.util.Vector;
public class VectorSafe {
private static Vectorvector=new Vector();
public static void main(String[] args) {
while(true)
{
for(int i=0; i<10; i++)
vector.add(i);
Thread removeThread = new Thread(new Runnable(){
public void run()
{
synchronized(vector)
{
for(int i=0; i2);
}
}
}
现在就不会出现数组越界的错误了
线程兼容
对象本身不是线程安全的,但是可以在调用端正确的使用同步手段来保证对象在并发环境中可以安全使用
线程对立
无论调用端是否采取了同步手段,它都无法在多线程被安全使用
互斥同步: 实现互斥同步有几种方法,临界区, 信号量,互斥量
不了解信号量,临界区可以看这篇文章进程同步与信号量以及死锁处理
在Java中实现互斥同步有两种方法
Synchronized关键字:经过编译后,会在同步块前后形成monitorenter和monitorexit两个字节码。
Synchronized我们前面用过很多次了,这里就不举例了
我们修改前面的程序,将synchronized换成ReentrantLock来实现
import java.util.Vector;
import java.util.concurrent.locks.ReentrantLock;
public class VectorSafe {
private static Vectorvector=new Vector();
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
while(true)
{
for(int i=0; i<10; i++)
vector.add(i);
Thread removeThread = new Thread(new Runnable(){
public void run()
{
lock.lock();
try
{
for(int i=0; i20);
}
}
}
上面的例子只是简单的介绍ReentrantLocK的使用方法
ReentrantLock效率要高于synchronized
前面使用的互斥同步,是一种较悲观的处理方式,它认为一定会出现多个线程同时处理这个对象,因此为了防止这种情况发生,它一次只允许一个线程访问对象,其他线程都要进入阻塞状态
而非阻塞同步就是一种较乐观的处理方式,它不让线程进入阻塞,当一个线程访问对象不能得到正确的结果,它就不断地重试,直到获得正确输出
如何使用非阻塞同步?
使用硬件处理器指令进行不断重试策略(JDK1.5以后)
也就是说,如果我们想要使用非阻塞同步,应该使用java已经实现非阻塞同步的类,因为其是直接使用硬件实现的,才能支撑不断重试所损耗的效率
举个例子
Class Counter
{
private volatile int count=0;
public synchronized void increment()
{
count++;
}
public int getCount()
{
return count;
}
}
这是阻塞同步,我们将其变成非阻塞同步
class Counter
{
private AtomicInteger count = new AtomicInteger();
public void increment()
{
count.incrementAndGet();
}
public in getCount()
{
return count.get();
}
}
使用AtomicInteger类的话不用使用阻塞同步依然能够实现线程安全
看一个实例
public class SequenceNumber {
//通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal seqNum = new ThreadLocal() {
public Integer initialValue() {
return 0;
}
};
public int getNextNum() //获得下一个序列值
{
seqNum.set(seqNum.get()+1);
return seqNum.get();
}
public static void main(String[] args) {
SequenceNumber sn = new SequenceNumber();
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
}
class TestClient extends Thread{
private SequenceNumber sn;
public TestClient(SequenceNumber sn)
{
this.sn = sn;
}
public void run()
{
for(int i=0; i<=2; i++)
System.out.println("Thread["+Thread.currentThread().getName()+"]sn["+sn.getNextNum()+"]");
}
}
输出结果
Thread[Thread-0]sn[1]
Thread[Thread-0]sn[2]
Thread[Thread-1]sn[1]
Thread[Thread-1]sn[2]
Thread[Thread-1]sn[3]
Thread[Thread-0]sn[3]
Thread[Thread-2]sn[1]
Thread[Thread-2]sn[2]
Thread[Thread-2]sn[3]
这个输出结果不唯一,但是每一个线程产生的序列号都是递增的,他们共享的是同一个SequenceNumber
,但是他们产生的序列值却不会互相干扰,这就实现了同步(不用使用synchronized),而这个序列值就是前面的线程本地存储,该值只在该线程中可见、
自旋锁
我在操作系统的一篇文章写过用户级线程和内核级线程的切换,用户级线程和内核级线程,看了这个只有你就会知道,切换线程其实对操作系统的负担是挺重的,因此我们希望当一个线程请求锁时,请求不到也在那里等一会(忙循环),一般是循环10次等待,看看等一会是否能得到锁
自适应锁
自适应自旋,前面的自选是固定时间的,而我们希望该线程能够根据上一个在该锁等待的线程等待的时间以及占有该锁的线程的状态来自适应的调整等待时间,如果上一个线程通过自选等待得到了锁,那么我们就认为这次等待可能还会成功,因此增加自旋时间
锁消除
定义:JVM即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除
判断依据:如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把它们当作栈上的数据对待,认为它们是线程私有的,同步加锁自然无需进行(ps:每一个线程都有一个属于自己的栈)
锁粗化
我们写代码的时候通常会将同步块的范围限制的尽量小,只在共享数据的实际作用域中才进行同步,这样是为了使得同步操作的数量尽可能变小
但是,还有另一种情况,如果你有一连串的代码都是对同一个对象反复加锁,那么就算没有线程争夺,依然会降低效率,因此我们会将这些区域用一个锁来处理,这就是锁粗化
偏向锁
目的:消除数据无竞争情况下的同步原语,进一步提高程序运行的性能,偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS都不做
偏向:意思是这个锁会偏向第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步
}
}
输出结果
Thread[Thread-0]sn[1]
Thread[Thread-0]sn[2]
Thread[Thread-1]sn[1]
Thread[Thread-1]sn[2]
Thread[Thread-1]sn[3]
Thread[Thread-0]sn[3]
Thread[Thread-2]sn[1]
Thread[Thread-2]sn[2]
Thread[Thread-2]sn[3]
这个输出结果不唯一,但是每一个线程产生的序列号都是递增的,他们共享的是同一个SequenceNumber
,但是他们产生的序列值却不会互相干扰,这就实现了同步(不用使用synchronized),而这个序列值就是前面的线程本地存储,该值只在该线程中可见、
自旋锁
我在操作系统的一篇文章写过用户级线程和内核级线程的切换,用户级线程和内核级线程,看了这个只有你就会知道,切换线程其实对操作系统的负担是挺重的,因此我们希望当一个线程请求锁时,请求不到也在那里等一会(忙循环),一般是循环10次等待,看看等一会是否能得到锁
自适应锁
自适应自旋,前面的自选是固定时间的,而我们希望该线程能够根据上一个在该锁等待的线程等待的时间以及占有该锁的线程的状态来自适应的调整等待时间,如果上一个线程通过自选等待得到了锁,那么我们就认为这次等待可能还会成功,因此增加自旋时间
锁消除
定义:JVM即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除
判断依据:如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把它们当作栈上的数据对待,认为它们是线程私有的,同步加锁自然无需进行(ps:每一个线程都有一个属于自己的栈)
锁粗化
我们写代码的时候通常会将同步块的范围限制的尽量小,只在共享数据的实际作用域中才进行同步,这样是为了使得同步操作的数量尽可能变小
但是,还有另一种情况,如果你有一连串的代码都是对同一个对象反复加锁,那么就算没有线程争夺,依然会降低效率,因此我们会将这些区域用一个锁来处理,这就是锁粗化
偏向锁
目的:消除数据无竞争情况下的同步原语,进一步提高程序运行的性能,偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS都不做
偏向:意思是这个锁会偏向第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步
清华大学Java教程p105-p124