线程概念
程序:就是应用程序,是静态的,通俗来讲就是我们写的代码,
进程:动态的程序,是程序的一次运行,也可以是进行中的程序,有自身的产生,存在,消亡
线程:线程是进程的实体,一进程可以多线程,比如qq的多个聊天窗口可以同时聊天
并发:多个任务交替执行,单核cpu实现多任务就是并发
并行:多个任务同时执行,并行是真正意义上的同步实现,多核cpu
区别:
每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。
进程是独立的,但线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
线程状态
/**
* @author 伍六七
* @date 2022/8/1 15:30
*/
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName()+"状态"+t.getState());
t.start();
while (Thread.State.TERMINATED!=t.getState()){
System.out.println(t.getName()+"状态"+t.getState());
Thread.sleep(500);
}
System.out.println(t.getName()+"状态"+t.getState());
}
}
class T extends Thread{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("执行次数"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程常用方法
线程优先级
每个线程都有优先级,优先级的高低只与线程获得执行机会的次数多少有关,并非是线程优先级越高的就一定先执行,因为哪个线程的先运行取决于CPU的调度,无法通过代码控制。
- MAX_PRIORITY=10,最高优先级
- MIN_PRIORITY=1,最低优先级
- NORM_PRIORITY=5,默认优先级
相关方法
- int getPriority() :返回线程的优先级。
- void setPriority(int newPriority) : 设置线程的优先级。
主线程的默认优先级为5,如果线程A创建线程B,那么A与B是相同的优先级,但由于不同操作系统的设置不同,不建议自定义优先级
Thread方法
创建Thread方法
/**
* @author 伍六七
* @date 2022/8/1 13:56
*/
public class SellTicket {
public static void main(String[] args) {
//Thread
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket01 sellTicket02 = new SellTicket01();
SellTicket01 sellTicket03 = new SellTicket01();
sellTicket01.start();
sellTicket02.start();
sellTicket03.start();
//Runnable
SellTicket02 sellTicket = new SellTicket02();
new Thread(sellTicket).start();
new Thread(sellTicket).start();
new Thread(sellTicket).start();
//lambda
new Thread(() -> {
System.out.println(1);
},"sellTicket00").start();
}
}
//使用Thread
class SellTicket01 extends Thread{
@Override
public void run() {
System.out.println(1);
}
}
//Runnable接口
class SellTicket02 implements Runnable{
@Override
public void run() {
System.out.println(1);
}
}
run()和start()
- run():普通方法重写,会阻塞,执行完该方法才向下走。
- start():启动线程->最终执行run方法。
调用run方法并没有开启新的线程,它仍旧是在主线程里
start()形成子线程原理
start0()是本地方法,JVM机调用
sleep() 方法和 wait()
先回顾一下线程休眠
线程休眠:让运行中的的线程暂停一段时间,进入计时等待状态。
- 调用sleep后,当前线程放弃CPU,进入计时等待状态,但仍然占用该cpu资源,在指定时间段之内,该线程不会获得执行的机会,此状态下的线程不会释放同步锁/同步监听器;
- wait是进入等待池等待,让出系统资源,其他线程可以占用cpu,一般wait不会加时间限制。
sleep方法和yield方法的区别:
- 共同点是都能使当前处于运行状态的线程放弃CPU时间片,把运行的机会给其他线程;
- 不同点在于:sleep方法会给其他线程运行机会,但是并不会在意其他线程的优先级;而yield方法只会给相同优先级或者更高优先级的线程运行的机会;
- 调用sleep方法后,线程进入计时等待状态,而调用yield方法后,线程进入就绪状态;
yeild() 方法和join()
- yeild():当线程A的yield方法执行后,线程不会释放锁,而是释放了CPU的执行权,将当前执行的线程重新变为就绪状态,让CPU重新选择要执行的线程,也有可能当前线程执行完yield方法后,CPU又一次选中这个线程执行。该方法主要用于调试或者测试,比如在多线程竞争条件下,让错误重现现象或者更加明显。
- join():当线程A调用线程B的join,那么系统会在此时将A置于等待状态先执行线程B,等到线程B执行完成后再开始继续执行线程A
用户线程与守护线程
用户线程与守护线程也被称为前台线程与后台线程
守护线程会伴随用户线程的结束而结束,其目的是为其他线程提供服务常见的:垃圾处理机制
设置线程为守护线程的方法,该方法必须在start方法之前调用,否则会触发IllegalThreadStateException异常,因为线程一旦启动,就无法对其做修改了。
- .setDaemon(true);
/**
* @author 伍六七
* @date 2022/8/1 14:31
*/
public class ThreadMethod_join {
public static void main(String[] args) throws InterruptedException {
ThreadTest01 threadTest01 = new ThreadTest01();
threadTest01.setDaemon(true);
threadTest01.start();
for (int i = 0; i < 5; i++) {
Thread.sleep(50);
System.out.println("主线程");
}
}
}
class ThreadTest01 extends Thread{
@Override
public void run() {
int i = 10;
while (i-->0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name+"执行了");
if(i==4){
Thread.yield();
}
}
}
}
如何预防和避免线程死锁?
售票员-资源抢夺冲突
package com.wuliuqi.ticket;
/**
* @author 伍六七
* @date 2022/8/1 13:56
*/
public class SellTicket {
public static void main(String[] args) {
//Thread
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket01 sellTicket02 = new SellTicket01();
SellTicket01 sellTicket03 = new SellTicket01();
sellTicket01.start();
sellTicket02.start();
sellTicket03.start();
}
class SellTicket01 extends Thread{
private static int ticketNum = 100;
@Override
public void run() {
while (ticketNum>0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()+"抢到了票"+"剩余票"+(--ticketNum));
}
}
}
超卖问题-没票时return
if(ticketNum<=0){
Loop=false;
System.out.println("没票了");
return;
}
synchronized解决资源冲突
注意用implements Runnable,extends Thread会重复
//用synchronized修饰方法
//不要修饰run方法,否则会一个线程买完所有票
public synchronized void Sell()
情况1-成功
修饰Sell方法,在run方法执行被修饰的Sell方法,解决了资源冲突
class SellTicket02 implements Runnable{
private static int ticketNum = 100;
boolean Loop = true;
public synchronized void Sell(){
if(ticketNum<=0){
Loop=false;
System.out.println("没票了");
return;
}
System.out.println("窗口"+Thread.currentThread().getName()+"抢到了票"+"剩余票"+(--ticketNum));
}
@Override
public void run() {
while (Loop){
Sell();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
情况2-独享
修饰run方法,实现了票的冲突,但是被独享了
class SellTicket02 implements Runnable{
private static int ticketNum = 100;
boolean Loop = true;
@Override
public synchronized void run() {
while (Loop){
if(ticketNum<=0){
Loop=false;
System.out.println("没票了");
return;
}
System.out.println("窗口"+Thread.currentThread().getName()+"抢到了票"+"剩余票"+(--ticketNum));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
锁
synchronized修饰方法-
//修饰方法
//对象锁
private static int ticketNum = 100;
boolean Loop = true;
public synchronized void Sell(){//synchronized修饰方法
if(ticketNum<=0){
Loop=false;
System.out.println("没票了");
return;
}
System.out.println("窗口"+Thread.currentThread().getName()+"抢到了票"+"剩余票"+(--ticketNum));
}
//用对象锁this
public void Sell(){
synchronized (this){
if(ticketNum<=0){
Loop=false;
System.out.println("没票了");
return;
}
System.out.println("窗口"+Thread.currentThread().getName()+"抢到了票"+"剩余票"+(--ticketNum));
}
}
//用引用锁Object
private static int ticketNum = 100;
boolean Loop = true;
private static Object lock=new Object(); //必须是静态的。
public void Sell(){
synchronized (lock){//synchronized(object)
if(ticketNum<=0){
Loop=false;
System.out.println("没票了");
return;
}
System.out.println("窗口"+Thread.currentThread().getName()+"抢到了票"+"剩余票"+(--ticketNum));
}
}
//用类锁class
public class SynchronizeMain {
}
class test{
public void Sell(){
synchronized (SynchronizeMain.class){//写类
if(ticketNum<=0){
Loop=false;
System.out.println("没票了");
return;
}
System.out.println("窗口"+Thread.currentThread().getName()+"抢到了票"+"剩余票"+(--ticketNum));
}
}
}
实例锁--引用锁(常用,多个对象同一把锁)--类锁
对象就是实例一个类可以有多个实例,每个实例都可以有自己的锁
同步局限性:执行效率降低
一个实例只能有一个锁对象,只能修饰方法和代码块。所以属于对象的,并且可以加锁的就是非静态方法和非静态代码块。
为什么要用 synchronized (object) 这种方式,去多创一个object对象
引用锁就是说在类中创建一个任意类型的引用,假如说你创建一个静态的,那么这个引用属于类本身,使用这个引用加锁,就相当于使用类加锁的效果,假如你创建一个非静态的引用,那么这个引用属于实例,每个实例都有,那么使用这个引用加锁,就相当于使用对象锁加锁的效果。
- 多线程对类的同一个实例的方法的同步,都属于对象锁的范畴。
- -----------------------------------------------------------
- 实例锁synchronized(this),锁的对象是this,是类的实例。
- 引用锁synchronized(object),锁的对象是object。
- 类锁sycronized(Account.class),加在类对象上.当sell加了sycronized(Account.class),则Account实例对象a和b调用sell(),都需要得到同一把Account类锁
- -----------------------------------------------------------
- 若线程A持有了Account类锁,线程B又去持有a实例的实例锁,这是允许的并不冲突
- syncronized修饰实例方法和代码块加锁的是当前实例对象this,静态方法加锁的是类class.
原因在于,synchronized(this)的锁对象是this,如果使用这种方式,一旦锁对象(实例)被别人获取,别人只要开个线程进行死循环 ,那你的Thread 1,这个正常的工作线程,就永远得不到执行,造成死锁。synchronized(object),锁的对象是object。所以,你拿到了sTest的实例锁,也不会影响到Thread1的正常执行。
/**
* @author 伍六七
* @date 2022/8/1 18:44
*/
//方式二--this---无输出
class STest{
public void print(){
synchronized (this){
System.out.println("xxxx");
}
}
}
//方式一--Object---成功输出
class STest{
private final Object object = new Object();
public void print(){
synchronized (object){
System.out.println("xxxx");
}
}
}
public class SynchronizeMain {
public static void main(String[] args) throws InterruptedException {
STest sTest = new STest();
// Thread 1
Thread t1 = new Thread(() -> {
sTest.print();
});
// Thread 2
Thread t2 = new Thread(() -> {
try { //这里拿到的是实例锁
synchronized (sTest){//当STest是引用锁时,采用反射使用sTest的同一把锁
while (true);
}
} catch (Exception e) {
System.out.println("Exception="+e.getMessage());
}
});
t2.start();
Thread.sleep(1000);
t1.start();
}
}
案例
卖票
public class ExerciseSell {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow(2000);
List<Thread> list = new ArrayList<>();
// 用来存储买出去多少张票
List<Integer> sellCount = new Vector<>();
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(() -> {
// 分析这里的竞态条件
int count = ticketWindow.sell(randomAmount());
sellCount.add(count);
});
list.add(t);
t.start();
}
list.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 买出去的票求和
log.debug("selled count:{}",sellCount.stream().mapToInt(c -> c).sum());
// 剩余票数
log.debug("remainder count:{}", ticketWindow.getCount());
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~5
public static int randomAmount() {
return random.nextInt(5) + 1;
}
}
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public int sell(int amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
}
转账
public class ExerciseTransfer {
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
a.transfer(b, randomAmount());
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
b.transfer(a, randomAmount());
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
// 查看转账2000次后的总金额
log.debug("total:{}",(a.getMoney() + b.getMoney()));
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~100
public static int randomAmount() {
return random.nextInt(100) +1;
}
}
class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public void transfer(Account target, int amount) {
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
死锁
jconsole
因此注意:主线程结束,进程不一定结束(子线程还在运行)