JAVA多线程知识
- 一、线程的基本概念
- 二、线程的创建
- (一)继承Thread类
- (二)实现Runnable接口
- (三)实现Callable接口
- (四)补充
- 三、线程状态
- (一)线程各状态间的关系:
- (二)sleep
- (三)join
- (四)yield
- (五)stop
- (六)priority
- (七)daemon
- (八)其它常用方法
- 四、线程同步
- (一)synchronized方法
- (二)synchronized块
- (三)死锁
- 五、线程协作
- (一)管道法
- (二)信号灯法
- 六、高级主题
- (一)任务定时调度
- (二)HappenBefore(指令重排)
- (三)Volatile
- (四)单例模式
- (五)可重入锁
- (六)CAS
一、线程的基本概念
- 线程就是独立的执行路径;
- 方法间的调用,即从哪里来到哪里去,是闭合的一条路径;多线程的使用则开辟了多条路径
- 在程序运行时,即使没有自己创建线程,后台也会存在多个线程,如gc进程、主线程(main为系统的入口点,用于执行整个程序);
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的;
- 很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核。如果是模拟出来的多线程,即一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换得很快,所以就有同时执行得错觉;
二、线程的创建
(一)继承Thread类
Thread的构造器:
例题:
package thread_study01;
/*
创建线程方式一:
1.创建:继承Thread+重写run
2.启动:创建子类对象+start
*/
public class StartThread extends Thread{
//线程入口点
public void run(){
for(int i=0;i<10000;i++){
System.out.println("一遍听课");
}
}
public static void main(String[] args){
//启动线程
//创建子类对象
StartThread st=new StartThread();
//启动
st.start();
//st.run();//普通方法调用
for(int i=0;i<10000;i++){
System.out.println("一边coding");
}
}
}
注意:
- 执行线程必须调用start(),加入到调度器中。
- 加入后不一定立即执行,系统安排调度分配执行。
- 直接调用run()不是开启多线程,是普通方法调用。
(二)实现Runnable接口
例题:
package thread_study01;
/*
创建线程方式二:
1.创建:实现Runnable+重写run
2.启动:创建实现类对象+Thread对象+start
推荐 避免单继承的局限性,优先使用接口
方便共享资源
*/
public class StartRun implements Runnable{
//线程入口点
public void run(){
for(int i=0;i<10000;i++){
System.out.println("一遍听课");
}
}
public static void main(String[] args){
new Thread(new StartRun()).start();
for(int i=0;i<10000;i++){
System.out.println("一边coding");
}
}
}
(三)实现Callable接口
例题:
package thread_study01;
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;
//了解创建线程的方式三:
public class CDownloader implements Callable<Boolean>{
private String url; //远程路径
private String name; //存储名字
public CDownloader(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception {
WebDownloader wd =new WebDownloader();
wd.download(url, name);
System.out.println(name);
return true;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
CDownloader cd1 =new CDownloader("http://upload.news.cecb2b.com/2014/0511/1399775432250.jpg","phone.jpg");
CDownloader cd2 =new CDownloader("http://p1.pstatp.com/large/403c00037462ae2eee13","spl.jpg");
CDownloader cd3 =new CDownloader("http://5b0988e595225.cdn.sohucs.com/images/20170830/d8b57e0dce0d4fa29bd5ef014be663d5.jpeg","success.jpg");
//创建执行服务:
ExecutorService ser=Executors.newFixedThreadPool(3);
//提交执行:
Future<Boolean> result1 =ser.submit(cd1) ;
Future<Boolean> result2 =ser.submit(cd2) ;
Future<Boolean> result3 =ser.submit(cd3) ;
//获取结果:
boolean r1 =result1.get();
boolean r2 =result1.get();
boolean r3 =result1.get();
System.out.println(r3);
//关闭服务:
ser.shutdownNow();
}
}
(四)补充
1.静态代理:
例题:
package thread_study01;
/**
* 静态代理
* 接口:
* 1.真实角色
* 2.代理角色
*/
public class StaticProxy {
public static void main(String[] args) {
new WeddingCompany(new You()).happyMarry();
}
}
//接口
interface Marry{
void happyMarry();
}
//真实角色
class You implements Marry{
@Override
public void happyMarry(){
System.out.println("......you and 嫦娥终于奔月了......");
}
}
//代理角色
class WeddingCompany implements Marry{
//真实角色
private Marry target;
public WeddingCompany(Marry target){
this.target=target;
}
@Override
public void happyMarry() {
ready();
target.happyMarry();
after();
}
private void ready(){
System.out.println("布置猪窝。。。。");
}
private void after(){
System.out.println("闹玉兔。。。。");
}
}
2.lambda:
lambda的是为了避免匿名内部类定义过多,其实质属于函数式编程的概念。
例题:
lambda表达式的推导过程
package thread_study01;
// Lambda表达式 简化线程(用一次)的使用
public class LambdaThread{
//静态内部类
static class Test implements Runnable{
public void run(){
for(int i=0;i<10000;i++){
System.out.println("一遍听课");
}
}
}
public static void main(String[] args){
new Thread(new Test()).start();
//局部内部类
class Test2 implements Runnable{
public void run(){
for(int i=0;i<10000;i++){
System.out.println("一遍听课");
}
}
}
new Thread(new Test2()).start();
//匿名内部类 必须借助接口或者父类
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10000;i++){
System.out.println("一遍听课");
}
}
}).start();
//jdk8 简化 lambda
new Thread(()->{
for(int i=0;i<10000;i++){
System.out.println("一遍听课");
}
}
).start();
}
}
三、线程状态
(一)线程各状态间的关系:
(二)sleep
package thread_study02;
// sleep模拟网络延时,放大了发生问题的可能性
public class BlockedSleep {
public static void main(String[] args){
//一份资源
Web12306 web=new Web12306();
//多个代理
new Thread(web,"码农").start();
new Thread(web,"码畜").start();
new Thread(web,"码蟥").start();
}
}
class Web12306 implements Runnable {
//票数
private int ticketNum = 1000;
@Override
public void run() {
while (true) {
if (ticketNum < 0)
break;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + ticketNum--);
}
}
}
- sleep(时间)指定当前线程阻塞的毫秒数;
- sleep存在异常InterruptedException;
- sleep到达后线程进入就绪状态
- 可模拟网络延迟、倒计时等
- 每一个对象都是一把锁,sleep不会释放锁
(三)join
join合并线程,待此线程执行完成后,再执行其它线程,其它线程阻塞
例题:
package thread_study02;
// join:合并线程, 插队线程
public class BlockedJoin01 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
for(int i=0;i<100;i++)
System.out.println("lambda..."+i);
});
t.start();
for (int i=0;i<100;i++){
if(i==20){
t.join();//插队 main被阻塞了
}
System.out.println("main"+i);
}
}
}
(四)yield
package thread_study02;
public class YieldDemo02 {
public static void main(String[] args) {
new Thread(()->{
for (int i=0;i<100;i++)
System.out.println("lambda...");
}).start();
for (int i=0;i<100;i++) {
if(i%20==0)
Thread.yield();//main礼让
System.out.println("main....");
}
}
}
- 礼让线程,让当前正在执行线程暂停
- 不是阻塞线程,而是将线程从运行状态转入就绪状态
- 让cpu调度器重新调度
(五)stop
- 不使用JDK提供的stop()/destroy()方法(他们本身也被JDK废弃了)
- 提供一个boolean型的终止变量,当这个变量置为false,则终止线程的运行
例题:
package thread_study02;
/**
* 终止线程
* 1.线程正常执行完毕-->次数
* 2.外部干涉-->标识
* 不要使用stop destroy
*/
public class TerminateThread implements Runnable {
//1.创建标识
private boolean flag=true;
private String name;
public TerminateThread(String name) {
this.name = name;
}
@Override
public void run() {
int i=0;
//关联标识,true-->运行 false-->停止
while (flag)
System.out.println(name+"-->"+i++);
}
//3.对外提供方法改变标识
public void terminate(){
flag=false;
}
public static void main(String[] args) {
TerminateThread tt=new TerminateThread("C罗");
new Thread(tt).start();
for(int i=0;i<=999;i++){
if(i==888){
tt.terminate();//线程的终止
System.out.println("tt game over");
}
System.out.println("main"+i);
}
}
}
(六)priority
优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程。
例子:
package thread_study02;
/**
* 线程的优先级1-10
* 1.NORM_PRIORITY 5
* 2.MIN_PRIORITY 1
* 3.MAX_PRIORITY 10
* 概率,不代表绝对先后
*/
public class PriorityTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getPriority());
MyPriority mp=new MyPriority();
Thread t1=new Thread(mp,"adidas");
Thread t2=new Thread(mp,"NINE");
Thread t3=new Thread(mp,"匡威");
Thread t4=new Thread(mp,"puma");
Thread t5=new Thread(mp,"李宁");
Thread t6=new Thread(mp,"回力");
//设置优先级要在运行前
t1.setPriority(10);
t2.setPriority(Thread.MAX_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t4.setPriority(1);
t5.setPriority(Thread.MIN_PRIORITY);
t6.setPriority(Thread.MIN_PRIORITY);
t1.start();t2.start();t3.start();t4.start();t5.start();t6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
Thread.yield();
}
}
(七)daemon
- 线程分为用户进程和守护进程
- 虚拟机必须确保用户进程执行完毕
- 虚拟机不用等待守护进程执行完毕
例子:
package thread_study02;
/**
* 守护线程:是为用户线程服务的;jvm停止不用等待守护线程执行完毕
* 默认:用户线程 jvm等待用户线程执行完毕
*/
public class DaemonTest {
public static void main(String[] args) {
God god=new God();
You you=new You();
Thread t=new Thread(god);
t.setDaemon(true);//将用户进程调整为守护进程
t.start();
new Thread(you).start();
}
}
class You implements Runnable{
@Override
public void run() {
for (int i=1;i<365*100;i++){
System.out.println("happy life...");
}
System.out.println("ooooooo");
}
}
class God implements Runnable{
@Override
public void run() {
for(;true;){
System.out.println("bless you");
}
}
}
(八)其它常用方法
例子:
package thread_study02;
/**
* 其他方法
* isAlive:线程是否还活着
* Thread.currentThread();当前线程
* setName、getName():代理名称
*/
public class InfoTest {
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().isAlive());
//设置名称:真实角色+代理角色
MyInfo info=new MyInfo("战斗机");
Thread t=new Thread(info);
t.setName("公鸡");
t.start();
Thread.sleep(1000);
System.out.println(t.isAlive());
}
}
class MyInfo implements Runnable{
private String name;
public MyInfo(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+name);
}
}
四、线程同步
(一)synchronized方法
synchronized方法控制对“成员变量|类变量”对象的访问:每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
例子:
package thread_study03;
/**
*线程安全:在并发时保证数据的正确性、效率尽可能高
* synchronized同步方法
*/
public class SynsafeTest01 {
public static void main(String[] args){
//一份资源
SafeWeb12306 web=new SafeWeb12306();
//多个代理
new Thread(web,"码农").start();
new Thread(web,"码畜").start();
new Thread(web,"码蟥").start();
}
}
class SafeWeb12306 implements Runnable {
//票数
private int ticketNum=10;
private boolean flag=true;
@Override
public void run() {
while(flag){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
test();
}
}
//线程安全 同步
public synchronized void test(){
if(ticketNum<0) {
flag = false;
return;
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNum--);
}
}
(二)synchronized块
同步块:synchronized(obj){ },obj称之为同步监视器
- obj可以是任何对象,但是推荐使用共享资源作为同步监视器。
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器是this即该对象本身,或class即类的模子。
同步监视器的执行过程 - 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器未锁,锁定并访问
例子:
package thread_study03;
public class SynBlockTest01 {
public static void main(String[] args) {
//账户
Account account=new Account(100,"结婚礼金");
SynDrawing you=new SynDrawing(account,80,"可悲的你");
SynDrawing wife=new SynDrawing(account,90,"happy的她");
you.start();
wife.start();
}
}
//模拟取款 线程安全
class SynDrawing extends Thread{
Account account;//取钱的账户
int drawingMoney;//取得钱数
int packetTotal=0;//口袋的总数
public SynDrawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
test();
}
//目标锁定account
public void test(){
//提高性能代码
if(account.money<=0){
return;
}
synchronized (account) {
if (account.money - drawingMoney < 0) {
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -= drawingMoney;
packetTotal += drawingMoney;
System.out.println(this.getName() + "口袋的钱为:" + packetTotal);
System.out.println(this.getName() + "账户余额为:" + account.money);
}
}
}
(三)死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。
例题:
package thread_study03;
/**
* 死锁: 过多的同步可能造成相互不释放资源
* 从而相互等待,一般发生于同步中持有多个对象的锁
* 避免: 不要在同一个代码块中,同时持有多个对象的锁
*/
public class DeadLock {
public static void main(String[] args) {
Markup g1 = new Markup(1,"张柏芝");
Markup g2 = new Markup(0,"王菲");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class Markup extends Thread{
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
//选择
int choice;
//名字
String girl;
public Markup(int choice,String girl) {
this.choice = choice;
this.girl = girl;
}
@Override
public void run() {
//化妆
markup();
}
//相互持有对方的对象锁-->可能造成死锁
private void markup() {
if(choice==0) {
synchronized(lipstick) { //获得口红的锁
System.out.println(this.girl+"涂口红");
//1秒后想拥有镜子的锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
synchronized(mirror) {
System.out.println(this.girl+"照镜子");
}*/
}
synchronized(mirror) {
System.out.println(this.girl+"照镜子");
}
}else {
synchronized(mirror) { //获得镜子的锁
System.out.println(this.girl+"照镜子");
//2秒后想拥有口红的锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
synchronized(lipstick) {
System.out.println(this.girl+"涂口红");
} */
}
synchronized(lipstick) {
System.out.println(this.girl+"涂口红");
}
}
}
}
五、线程协作
线程通信
(一)管道法
例题:
package thread_study04;
/**
* 协作模型:生产者消费者实现方式一:管程法
* 借助缓冲区
*/
public class CoTest01 {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container ;
public Productor(SynContainer container) {
this.container = container;
}
public void run() {
//生产
for(int i=0;i<100;i++) {
System.out.println("生产-->"+i+"个馒头");
container.push(new Steamedbun(i) );
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container ;
public Consumer(SynContainer container) {
this.container = container;
}
public void run() {
//消费
for(int i=0;i<100;i++) {
System.out.println("消费-->"+container.pop().id+"个馒头");
}
}
}
//缓冲区
class SynContainer{
Steamedbun[] buns = new Steamedbun[10]; //存储容器
int count = 0; //计数器
//存储 生产
public synchronized void push(Steamedbun bun) {
//何时能生产 容器存在空间
//不能生产 只有等待
if(count == buns.length) {
try {
this.wait(); //线程阻塞 消费者通知生产解除
} catch (InterruptedException e) {
}
}
//存在空间 可以生产
buns[count] = bun;
count++;
//存在数据了,可以通知消费了
this.notifyAll();
}
//获取 消费
public synchronized Steamedbun pop() {
//何时消费 容器中是否存在数据
//没有数据 只有等待
if(count == 0) {
try {
this.wait(); //线程阻塞 生产者通知消费解除
} catch (InterruptedException e) {
}
}
//存在数据可以消费
count --;
Steamedbun bun = buns[count] ;
this.notifyAll(); //存在空间了,可以唤醒对方生产了
return bun;
}
}
//馒头
class Steamedbun{
int id;
public Steamedbun(int id) {
this.id = id;
}
}
提示:与sleep不同,wait会释放锁
(二)信号灯法
例题:
package thread_study04;
/**
* 协作模型:生产者消费者实现方式二:信号灯法
* 借助标志位
*/
public class CoTest02 {
public static void main(String[] args) {
Tv tv =new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者 演员
class Player extends Thread{
Tv tv;
public Player(Tv tv) {
this.tv = tv;
}
public void run() {
for(int i=0;i<20;i++) {
if(i%2==0) {
this.tv.play("奇葩说");
}else {
this.tv.play("太污了,喝瓶立白洗洗嘴");
}
}
}
}
//消费者 观众
class Watcher extends Thread{
Tv tv;
public Watcher(Tv tv) {
this.tv = tv;
}
public void run() {
for(int i=0;i<20;i++) {
tv.watch();
}
}
}
//同一个资源 电视
class Tv{
String voice;
//信号灯
//T 表示演员表演 观众等待
//F 表示观众观看 演员等待
boolean flag = true;
//表演
public synchronized void play(String voice) {
//演员等待
if(!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//表演
System.out.println("表演了:"+voice);
this.voice = voice;
//唤醒
this.notifyAll();
//切换标志
this.flag =!this.flag;
}
//观看
public synchronized void watch() {
//观众等待
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//观看
System.out.println("听到了:"+voice);
//唤醒
this.notifyAll();
//切换标志
this.flag =!this.flag;
}
}
六、高级主题
(一)任务定时调度
例题一:
package thread_study04;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Timer;
import java.util.TimerTask;
// 任务调度: Timer(本身就是一个线程) 和TimerTask类(实现了Runnable接口,具备多线程的能力)
public class TimerTest01 {
public static void main(String[] args) {
Timer timer = new Timer();
//执行安排
//timer.schedule(new MyTask(), 1000); //执行任务一次
//timer.schedule(new MyTask(), 1000,200); //执行多次
Calendar cal = new GregorianCalendar(2099999,12,31,21,53,54);
timer.schedule(new MyTask(), cal.getTime(),200); //指定时间
}
}
//任务类
class MyTask extends TimerTask{
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println("放空大脑休息一会");
}
System.out.println("------end-------");
}
}
例题二(比较难,暂时先做了解):
package thread_study04;
import static org.quartz.DateBuilder.evenSecondDateAfterNow;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
// quartz学习入门
public class QuartzTest {
public void run() throws Exception {
// 1、创建 Scheduler的工厂
SchedulerFactory sf = new StdSchedulerFactory();
//2、从工厂中获取调度器
Scheduler sched = sf.getScheduler();
// 3、创建JobDetail
JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();
// 时间
Date runTime = evenSecondDateAfterNow();
// 4、触发条件
//Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime)
.withSchedule(simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3)).build();
// 5、注册任务和触发条件
sched.scheduleJob(job, trigger);
// 6、启动
sched.start();
try {
// 100秒后停止
Thread.sleep(100L * 1000L);
} catch (Exception e) {
}
sched.shutdown(true);
}
public static void main(String[] args) throws Exception {
QuartzTest example = new QuartzTest();
example.run();
}
}
(二)HappenBefore(指令重排)
你写的代码很可能根本没按你期望的顺序执行,因为编译器和CPU会尝试重排指令使得代码更快地运行
例题:
package thread_study04;
/**
* 指令重排: 代码执行顺序与预期不一致
* 目的:提高性能
*/
public class HappenBefore {
//变量1
private static int a = 0;
//变量2
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<10;i++) {
a = 0;
flag = false;
//线程1 更改数据
Thread t1 = new Thread(()->{
a = 1;
flag = true;
}) ;
//线程2 读取数据
Thread t2 = new Thread(()->{
if(flag) {
a *=1;
}
//指令重排
if(a == 0) {
System.out.println("happen before a->"+a);
}
}) ;
t1.start();
t2.start();
//合并线程
t1.join();
t2.join();
}
}
}
(三)Volatile
- 线程对变量进行修改之后,要立刻写回主内存;
- 线程对变量读取的时候,要从主内存中读,而不是缓存。
- 它不能保证原子性。
例题:
package com.sxt.others;
// volatile用于保证数据的同步,也就是可见性
public class VolatileTest {
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while(num==0) { //此处不要编写代码
}
}) .start();
Thread.sleep(1000);
num = 1;
}
}
(四)单例模式
ThreadLocal能够放一个线程级别的变量,其本身能够被多个线程共享使用,并且又能够达到线程安全的目的。说白了,ThreadLocal就是想在多线程环境下去保证成员变量的安全。
例题:
package com.sxt.others;
/**
* ThreadLocal:每个线程自身的存储本地、局部区域
* get/set/initialValue
*/
public class ThreadLocalTest01 {
//private static ThreadLocal<Integer> threadLocal = new ThreadLocal<> ();
//更改初始化值
/*private static ThreadLocal<Integer> threadLocal = new ThreadLocal<> () {
protected Integer initialValue() {
return 200;
};
};*/
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()-> 200);
public static void main(String[] args) {
//获取值
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
//设置值
threadLocal.set(99);
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
new Thread(new MyRun()).start();
new Thread(new MyRun()).start();
}
public static class MyRun implements Runnable{
public void run() {
threadLocal.set((int)(Math.random()*99));
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
}
}
}
- 每个线程自身的数据,更改不会影响其他线程
- ThreadLocal要分析上下文 环境 起点1.构造器: 哪里调用 就属于哪里 找线程体 2.run方法:本线程自身的
- InheritableThreadLocal:继承上下文 环境的数据 ,拷贝一份给子线程
(五)可重入锁
锁作为并发共享数据保证一致性的工具,大多数内置锁都是可重入的,也就是说,如果某个线程试图获取一个已经由他持有的锁时,那么这个请求会立刻成功,并且会将这个锁的计数值加1,而当线程退出同步代码块时,计数器将会递减,当计数值等于0时,锁释放。如果没有可重入锁额支持,在第二次企图获得锁时将会进入死锁状态。
例题:
package com.sxt.others;
// 可重入锁: 锁可以延续使用 + 计数器
public class LockTest03 {
ReLock lock = new ReLock();
public void a() throws InterruptedException {
lock.lock();
System.out.println(lock.getHoldCount());
doSomething();
lock.unlock();
System.out.println(lock.getHoldCount());
}
//不可重入
public void doSomething() throws InterruptedException {
lock.lock();
System.out.println(lock.getHoldCount());
//...................
lock.unlock();
System.out.println(lock.getHoldCount());
}
public static void main(String[] args) throws InterruptedException {
LockTest03 test = new LockTest03();
test.a();
Thread.sleep(1000);
System.out.println(test.lock.getHoldCount());
}
}
// 可重入锁
class ReLock{
//是否占用
private boolean isLocked = false;
private Thread lockedBy = null; //存储线程
private int holdCount = 0;
//使用锁
public synchronized void lock() throws InterruptedException {
Thread t = Thread.currentThread();
while(isLocked && lockedBy != t) {
wait();
}
isLocked = true;
lockedBy = t;
holdCount ++;
}
//释放锁
public synchronized void unlock() {
if(Thread.currentThread() == lockedBy) {
holdCount --;
if(holdCount ==0) {
isLocked = false;
notify();
lockedBy = null;
}
}
}
public int getHoldCount() {
return holdCount;
}
}
(六)CAS
锁分为两类:
- 悲观锁:synchronized是独占锁,即悲观锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
- 乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,知道成功为止。
例题:
package com.sxt.others;
import java.util.concurrent.atomic.AtomicInteger;
// CAS:比较并交换
public class CAS {
//库存
private static AtomicInteger stock = new AtomicInteger(5);
public static void main(String[] args) {
for(int i=0;i<5;i++) {
new Thread(()->{
//模拟网络延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Integer left = stock.decrementAndGet();
if(left<1) {
System.out.println("抢完了...");
return ;
}
System.out.print(Thread.currentThread().getName()+"抢了一件商品");
System.out.println("-->还剩"+left);
}) .start();
}
}
}
- 有三个值一个当前值V、旧的预期值A、将更新的值B。首先要获取内存当中当前的内存值V,再将内存值V和原值A作比较,要是相等就修改为要修改的值B并返回true,否则什么也不做并返回false。
- CAS是一组原子操作,不会被外部打断。
- 属于硬件级别的操作,效率比加锁高。