java多线程基础
串行与并发
串行: 多个任务依次执行,第一个没完成,无法执行第二个·,
并发:多个任务同时执行
进程和线程
进程是对一个程序运行所需要占用的资源的描述。进程可以实现多个任务并发执行,但是我们一般不会去开发多个进程,因为多个进程执行的资源是不共享的。
线程:
线程是一个任务执行的多个单元,多个工厂可以理解为多个进程,而工程里面的多个流水线就是线程。线程之间的数据是可以共享的。进程包含线程,一个进程中是可以有多个线程的·。
生命周期
在程序开发中,将一个对象从实例化完成,到这个对象使用结束,并销毁,将这个过程称为生命周期。
线程的生命周期:
一个线程被实例化完成,到这个线程被消耗,中间的过程。
线程的状态:
新生态
一个线程对象被实例化完成,但是没有做任何操作
就绪态
一个线程被开启,已经开始争抢cpu时间片
运行态
一个线程抢到了cpu时间片,已经开始执行这个线程中的逻辑
阻塞态
现在在运行过程中,受到了某些操作的影响,放弃了已经获取到的时间片。并且不再参与时间片的争抢,此时线程处于挂起态
死亡态
线程被销毁。
线程的开辟方式
1、继承Thread类,然后重写run方法
public class Test extends Thread {
@Override
public void run() {
System.out.println("继承thread类,然后重写run方法的方式");
}
public static void main(String[] args) {
Test threa =new Test();
//启动
// start方法会开启一个新的线程,来执行run中的逻辑
//如果· 直接调用run方法。则线程不会进入就绪态
threa.start();
}
}
2、实现Runnable接口,重写run()方法
ava只支持类的单继承,如果某个类已经继承了其他父类,就无法再继承Thread类来实现多线程。在这种情况下,就可以考虑通过实现Runnable接口的方式来实现多线程。
public class ThreadTest implements Runnable {
@Override
public void run() {
for (int i=0;i<10; i++){
System.out.println("小陈");
}
}
public static void main(String[] args) {
ThreadTest t1=new ThreadTest();
Thread t2=new Thread(t1);
t2.start();
}
}
在以后的大多数开发中,我们都会采用实现接口的方法
通过实现Runnable或Callable接口,相对于继承Thread类实现多线程来说:
适合多个线程去处理同一个共享资源的情况
可以避免Java单根继承带来的局限性
事实上,实际开发中大部分的多线程应用都会采用Runnable接口或者Callable接口的方式实现多线程。
比如下面这个售票的模拟:
虽然可能出现问题,但是我们可以通过控制临界资源,来解决
public class ThreadTest implements Runnable {
private int tk=0;
@Override
public void run() {
while (tk<100){
System.out.println(Thread.currentThread().getName()+"出售了第"+tk+"票");
tk++;
}
}
public static void main(String[] args) {
ThreadTest t1=new ThreadTest();
Thread t2=new Thread(t1,"k1");
Thread t3=new Thread(t1,"k2");
t2.start();
t3.start();
}
}
如果采用继承的方法,就会出现两个窗口售卖同一张票
线程休眠
使用
1、Thread.sleep(200);
2、TimeUnit.MILLISECONDS.sleep(100);
如果在多线程任务中,使用这个休眠,可能会导致资源被重复调用的情况,因为线程在休眠时,是不会让出资源的
设置线程的优先级
设置线程优先级,只是修改这个线程抢到cpu时间片的概率,并不是优先级高得线程一定能抢到cpu得时间片,优先级得设置是一个整数,默认是五,
public static void main(String[] args) {
ThreadTest t1=new ThreadTest();
Thread t2=new Thread(t1,"k1");
Thread t3=new Thread(t1,"k2");
//设置优先级别
//设置优先级一定要放在这个线程开始执行之前
t2.setPriority(10);
t3.setPriority(1);
t2.start();
t3.start();
}
线程的礼让
线程礼让,是指,让当前自己正在运行的线程释放自己的cpu资源,由运行状态回到就绪状态。
临界资源
线程与进程最大的区别是:多个线程之间可以资源共享,而多个进程之间资源是不能共享的。
在线程中,共享的资源就叫临界资源。
上面我们说到,多线程售票,会出现两个窗口卖同一张票的问题,
产生原因:
是因为多线程太快了,当一个线程抢到时间片,正准备修改剩余票数时,另一个窗口又抢到了,这时,上一个线程还未修改,而这个线程已经拿到剩余的数据了,就会出现两个窗口售卖同一张票这个问题了。
根本原因就是:
有多个线程同时去操作这个临界资源
解决方法:
不让多个线程同时去操作临界资源
这里引入锁的概念,当一个线程在操作资源时,就放一把锁,而其他线程要操作资源是,看到资 源上面有锁,便开始等待,不能操作,当线程1使用完毕后,锁被打开,另一个线程再开始操作。
平时我们开发时,还会用到其他方法:
1、同步代码段
2、同步方法
3、同步锁
1、同步代码段:
public class SourceConfilct implements Runnable {
//票数
public static int restCount=100;
@Override
public void run() {
while(restCount>0){
//对象锁:
//类锁
//这里需要保证的时,多个线程看到的锁,是同一个锁
synchronized (""){
if(restCount<=0){
return;
}
System.out.println(Thread.currentThread().getName()+"卖出1张票"+"剩余"+--restCount+"张");
}
}
}
public static void main(String[] args) {
//用四个线程,模拟四个售票员
SourceConfilct s1=new SourceConfilct();
Thread t1=new Thread(s1,"1号");
Thread t2=new Thread(s1,"2号");
t1.start();
t2.start();
}
}
2、同步方法
同步的方法:
静态方法:同步锁就是类锁 当前类 .class
非静态方法:同步锁就是this
public class Test2 implements Runnable {
//票数
public static int restCount=100;
//1、同步方法 使用关键字,synchronized修饰的方法 就是同步方法
public synchronized static void mp() {
if (restCount <= 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "卖出1张票" + "剩余" + --restCount + "张");
}
@Override
public void run() {
while(restCount>0){
mp();
}
}
public static void main(String[] args) {
//用四个线程,模拟四个售票员
SourceConfilct s1=new SourceConfilct();
Thread t1=new Thread(s1,"1号");
Thread t2=new Thread(s1,"2号");
t1.start();
t2.start();
}
}
3、同步锁
public class Test3 implements Runnable {
//票数
public static int restCount=100;
@Override
public void run() {
/**
* 实例化一个锁
*/
ReentrantLock lock=new ReentrantLock();
while(restCount>0){
lock.lock(); //上锁
try {
System.out.println(Thread.currentThread().getName() + "卖出1张票" + "剩余" + --restCount + "张");
}catch (Exception e){
lock.unlock(); //解锁
}finally {
lock.unlock();
}
lock.unlock();
}
}
public static void main(String[] args) {
//1、同步方法 使用关键字,synchronized修饰的方法 就是同步方法
//用四个线程,模拟四个售票员
SourceConfilct s1=new SourceConfilct();
Thread t1=new Thread(s1,"1号");
Thread t2=new Thread(s1,"2号");
t1.start();
t2.start();
}
使用锁的时候,要注意避免死锁
死锁:多个线程彼此持有对方需要的锁对象,而不释放自己的锁。
死锁代码如下:
public class Dome1 {
public static void main(String[] args) {
//死锁 :多个线程彼此持有对方锁需要的锁
Runnable runnable=() ->{
synchronized ("A"){
System.out.println("A线程持有a锁,等待B锁");
synchronized ("B"){
System.out.println("A线程同时持有了A锁和b锁");
}
}
};
Runnable runnabletow=() ->{
synchronized ("B"){
System.out.println("b线程持有B锁,等待a锁");
synchronized ("A"){
System.out.println("b线程同时持有了A锁和b锁");
}
}
};
Thread t1=new Thread(runnable);
Thread t2=new Thread(runnabletow);
t1.start();
t2.start();
}
}
那么在实际业务中,我们遇到这样的需求,需要持有一个锁标记,又需要另一个线程的锁标记时,我们应该怎么避免死锁呢
可以使用以下方法
// wait :是object类中的一个方法,让当前的线程释放自己的锁标记,并释放cpu资源 ,使当前的线程进入等待队列中
//notify :通知 是object类中的一个方法,作用是唤醒等待队列中的一个线程,使这个线程进入锁池
// notfyAll :通知 是object类中的一个方法,作用是唤醒等待队列中所有的线程,使这些线程进入锁池
public class Dome1 {
public static void main(String[] args) {
Runnable runnable=() ->{
synchronized ("A"){
System.out.println("A线程持有a锁,等待B锁");
try {
"A".wait(); //让A 等待,并释放A锁
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("B"){
System.out.println("A线程同时持有了A锁和b锁");
}
}
};
Runnable runnabletow=() ->{
synchronized ("B"){
System.out.println("b线程持有B锁,等待a锁");
synchronized ("A"){
System.out.println("b线程同时持有了A锁和b锁");
"A".notify(); //B执行完了唤醒A锁
}
}
};
Thread t1=new Thread(runnable);
Thread t2=new Thread(runnabletow);
t1.start();
t2.start();
}
}
浙公网安备 33010602011771号