1.继承Thread类
本质是通过实现Runnable接口的一个实例,代表一个线程的实例
public class MyThread extends Thread{
public void run(){
System.out.println("run()")
}
}
MyThread thread = new MyThread();
thread.start();
2.实现Runnable接口
public class MyThread extends OtherClass implements Runnable{
public void run(){
System.out.println("run()")
}
}
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
新建->就绪->运行->阻塞->死亡
1.新建:程序使用new创建线程后,就是新建状态,jvm会为他分配内存,并初始化成员变量的值
2.就绪:线程对象调用start()后,就是就绪状态。java虚拟机会创建方法调用栈和程序计数器,等待调度运行
3.运行:处于就绪状态的线程获得了cpu,执行run()的线程执行体,就是运行状态
补充:线程什么情况会从运行状态变成阻塞状态
4.阻塞:线程因为某种原因放弃了对cpu的使用权,即让出了cpu timeslice,暂时停止运行,直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice转到运行状态
等待阻塞、同步阻塞、其他阻塞
5.死亡:
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新(说明没有人去修改过数据,所以更新),不一样算提交失败),如果失败则要重复读-比较-写的操作。java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock。
如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
出现的原因:如果存在大量互斥同步代码,当出现高并发的时候,系统内核态就需要不断地去挂起线程和恢复线程,频繁的操作就会对系统的并发性能有一定的影响
java 非公平自旋锁
/**
* 锁的持有者
*/
private AtomicReference<Thread> owner = new AtomicReference<>();
/**
* 记录锁重入次数
*/
private volatile int count = 0;
//volatile对其他线程的可见性
public void lock(){
Thread thread = Thread.currentThread();
if (thread == owner.get()){//获取AtomicReference的当前对象引用值。
count ++;
return;
}
/**
原子性地更新AtomicReference内部的value值,
其中expect代表当前AtomicReference的value值,update则是需要设置的新引用值。
该方法会返回一个boolean的结果,
当expect和AtomicReference的当前值不相等时,修改会失败,返回值为false,
若修改成功则会返回true。
**/
while (!owner.compareAndSet(null,thread));
}
public void unlock(){
Thread thread = Thread.currentThread();
if (thread == owner.get()){
if (count > 0) count --; //锁重入, 直接自减即可
else owner.set(null);//设置AtomicReference最新的对象引用值,该新值的更新对其他线程立即可见。
}
}
public static void main(String[] args) {
TestController test = new TestController();
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始尝试获取自旋锁");
test.lock();
try {
System.out.println(Thread.currentThread().getName() + "获取到了自旋锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
test.unlock();
System.out.println(Thread.currentThread().getName() + "释放了了自旋锁");
}
}
};
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
t1.start();
t2.start();
t3.start();
}
java 公平锁自旋锁
/**
* 当前持有锁的号码
*/
private AtomicInteger serviceNum = new AtomicInteger(0);
/**
* 记录锁重入次数
*/
private volatile int count = 0;//volatile对其他线程的可见性
/**
* 排队号码
*/
private AtomicInteger ticketNum = new AtomicInteger(0);
/**
* 各线程存放自己所申请的排队号码
*/
private static ThreadLocal<Integer> threadLocalNum = new ThreadLocal<>();
public void lock(){
Integer num = threadLocalNum.get();
if (num != null && num == serviceNum.get()){
//当前线程已经持有锁, 则记录重入次数即可
count ++;
return;
}
//申请一个排队号码
num = requestTickerNum();
System.out.println("申请一个排队号码:"+num);
threadLocalNum.set(num);
//自旋等待,直到该排队号码与serviceNum相等
while (num != this.serviceNum.get());
}
private Integer requestTickerNum() {
return ticketNum.getAndIncrement();
}
public void unlock(){
Integer num = threadLocalNum.get();
if (num != null && num == serviceNum.get()){
if (count > 0) count--;//锁重入, 直接自减即可
else{
threadLocalNum.remove();
//自增serviceNum, 以便下一个排队号码的线程能够退出自旋
serviceNum.set(num + 1);
}
}
}
public static void main(String[] args) {
TestController test = new TestController();
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始尝试获取自旋锁");
test.lock();
try {
System.out.println(Thread.currentThread().getName() + "获取到了自旋锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
test.unlock();
System.out.println(Thread.currentThread().getName() + "释放了了自旋锁");
}
}
};
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
Thread t4 = new Thread(runnable);
t1.start();
t2.start();
t3.start();
t4.start();
}
当多个线程同时访问同一个数据时,容易出现问题,为了避免,要保证线程同步互斥(指并发执行的多个线程),在同一时间内值允许一个线程访问共享数据。使用synchronized来获取一个对象的同步锁
多个线程同时被阻塞,他们中的一个或者全部都在等待某个资源被释放
产生死锁的条件:(下面同时存在才是死锁)
常见死锁例子:进程a中包含资源a,进程b中包含资源b,a的下一步需要资源b,b的下一步需要资源a,所以他们就互相等待对方占有的资源释放,所以产生一个循环等待死锁
解决方法:
产生死锁:
情况一:
/**
* 一个简单的死锁类
* t1先运行,这个时候flag==true,先锁定obj1,然后睡眠1秒钟
* 而t1在睡眠的时候,另一个线程t2启动,flag==false,先锁定obj2,然后也睡眠1秒钟
* t1睡眠结束后需要锁定obj2才能继续执行,而此时obj2已被t2锁定
* t2睡眠结束后需要锁定obj1才能继续执行,而此时obj1已被t1锁定
* t1、t2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
*/
public class DeadLock implements Runnable{
private static Object obj1 = new Object();
private static Object obj2 = new Object();
private boolean flag;
public DeadLock(boolean flag){
this.flag = flag;
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + "运行");
if(flag){
synchronized(obj1){
System.out.println(Thread.currentThread().getName() + "已经锁住obj1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj2){
// 执行不到这里
System.out.println("1秒钟后,"+Thread.currentThread().getName()
+ "锁住obj2");
}
}
}else{
synchronized(obj2){
System.out.println(Thread.currentThread().getName() + "已经锁住obj2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj1){
// 执行不到这里
System.out.println("1秒钟后,"+Thread.currentThread().getName()
+ "锁住obj1");
}
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLock(true), "线程1");
Thread t2 = new Thread(new DeadLock(false), "线程2");
t1.start();
t2.start();
}
情况二:
public class SyncThread implements Runnable{
private Object obj1;
private Object obj2;
public SyncThread(Object o1, Object o2){
this.obj1=o1;
this.obj2=o2;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (obj1) {
System.out.println(name + " acquired lock on "+obj1);
work();
synchronized (obj2) {
System.out.println("After, "+name + " acquired lock on "+obj2);
work();
}
System.out.println(name + " released lock on "+obj2);
}
System.out.println(name + " released lock on "+obj1);
System.out.println(name + " finished execution.");
}
private void work() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = new Object();
Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
t1.start();
Thread.sleep(1000);
t2.start();
Thread.sleep(1000);
t3.start();
}
让当前正在执行的线程暂停一段时间,并进入阻塞状态(使用Thread.sleep()方法)
当睡眠结束后,重新进入到就绪状态。
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
thread.sleep(1000);//睡眠的是mian线程,不是MyThread 线程
Thread.sleep(10);
for(int i=0;i<100;i++){
System.out.println("main"+i);
}
}
让当前正在执行的线程暂停,让出cpu资源给其他线程。yield后进入就绪状态。(可能出现:线程调度器又将其调度出来重新进入到运行状态运行)
当调用yield()方法暂停后,优先级比当前线程相同或者高时,就绪状态的线程更有可能获得执行机会
public class Test1 {
public static void main(String[] args) throws InterruptedException {
new MyThread("低级", 1).start();
new MyThread("中级", 5).start();
new MyThread("高级", 10).start();
}
}
class MyThread extends Thread {
public MyThread(String name, int pro) {
super(name);// 设置线程的名称
this.setPriority(pro);// 设置优先级
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(this.getName() + "线程第" + i + "次执行!");
if (i % 5 == 0)
Thread.yield();
}
}
}
运行结果:先把高级的执行完再执行中级最后执行低级
sleep()和yield()的区别
将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时,join方法可以完成这个功能。
void join()
当前线程等该加入该线程后面,等待该线程终止。
void join(long millis)
当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,
那么当前线程进入就绪状态,重新等待cpu调度
void join(long millis,int nanos)
等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。如果在millis时间内,该线程没有执行完,
那么当前线程进入就绪状态,重新等待cpu调度
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级。
注:Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,范围是1~·0之间,也可以使用Thread类提供的三个静态常量:
public class Test1 {
public static void main(String[] args) throws InterruptedException {
new MyThread("高级", 10).start();
new MyThread("低级", 1).start();
}
}
class MyThread extends Thread {
public MyThread(String name,int pro) {
super(name);//设置线程的名称
setPriority(pro);//设置线程的优先级
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + "线程第" + i + "次执行!");
}
}
}
守护线程是指在程序运行的时候在后台提供一种通用服务的线程
守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。守护线程的用途为:
setDaemon方法的详细说明:
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
参数:
on - 如果为 true,则将该线程标记为守护线程。
抛出:
IllegalThreadStateException - 如果该线程处于活动状态。
SecurityException - 如果当前线程无法修改该线程。
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作前,被其他线程调用,从而保证该变量的唯一性和准确性。
synchronized修饰方法,java每个对象都有一个内置锁,当用了synchronized修饰后,内置锁会保护整个方法。
public synchronized void save(){}
synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
public class Bank {
private int count =0;//账户余额
//存钱
public void addMoney(int money){
synchronized (this) {
count +=money;
}
System.out.println(System.currentTimeMillis()+"存进:"+money);
}
//取钱
public void subMoney(int money){
synchronized (this) {
if(count-money < 0){
System.out.println("余额不足");
return;
}
count -=money;
}
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}
//查询
public void lookMoney(){
System.out.println("账户余额:"+count);
}
}
public class SynchronizedThread {
class Bank {
private volatile int account = 100;
public int getAccount() {
return account;
}
/**
* 用同步方法实现
*
* @param money
*/
public synchronized void save(int money) {
account += money;
}
/**
* 用同步代码块实现
*
* @param money
*/
public void save1(int money) {
synchronized (this) {
account += money;
}
}
}
class NewThread implements Runnable {
private Bank bank;
public NewThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// bank.save1(10);
bank.save(10);
System.out.println(i + "账户余额为:" +bank.getAccount());
}
}
}
/**
* 建立线程,调用内部类
*/
public void useThread() {
Bank bank = new Bank();
NewThread new_thread = new NewThread(bank);
System.out.println("线程1");
Thread thread1 = new Thread(new_thread);
thread1.start();
System.out.println("线程2");
Thread thread2 = new Thread(new_thread);
thread2.start();
}
public static void main(String[] args) {
SynchronizedThread st = new SynchronizedThread();
st.useThread();
}
}
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
//只给出要修改的代码,其余代码与上同
class Bank {
private int account = 100;
//需要声明这个锁
private Lock lock = new ReentrantLock();
public int getAccount() {
return account;
}
//这里不再需要synchronized
public void save(int money) {
lock.lock();
try{
account += money;
}finally{
lock.unlock();
}
}
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。