Java多线程基本概念

Java多线程理解

1.程序、进程、线程

1.1什么是程序,进程,线程?

程序:是为了完成待定任务、用某种语言编写的一组指令的集合。即指 一段静态代码 ,静态对象
进程:是程序的一次执行过程,或是 正在运行的一个程序 。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
->如:运行中的QQ,运行中的MP3播放器…
-> main() --main主线程、gc垃圾回收、异常处理线程
** 线程: 进程可进一步细化为线程,是一个程序内部的一条执行路径。
->若一个进程同一时间并执行多个线程,就是支持多线程的**

2.多线程的优点

1.提高应用程序的响应。对图像化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率。
3.改善程序结构。即长又复杂的进程分为多个线程,独立运行,利于理解和修改。

3.判断是否为多线程

public class Sample{
		public void method1(String str){
			System.out.println(str);
		}
	
	public void method2(String str){
		method1(str);
	}
	
	public static void main(String[] args){
		Sample s = new Sample();
		s.method2("hello");
	}
}

4.线程的创建和使用

利用继承的方式创建线程 第一种:

class MyThread extends Thread{

 //    重写run()
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            if( i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
    
    //    创建Thread类的子类的创建的对象
        Thread t1 = new MyThread(); 
        t1.start();
        System.out.println("hello");
    }
}

利用实现Runable接口方式创建线程 第二种:

class MThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            if( i % 2==0){
                System.out.println(i);
            }
        }
    }
}
public class ThreadTest2 {
    public static void main(String[] args) {
        MThread mThread = new MThread();
        Thread t1 = new Thread(mThread);
        t1.start();
    }
}

比较创建线程的两种方式:
开发中,优先选择实现Runable接口的方式
原因1:实现的方式没有类的单继承性的局限性
2:实现的方式更适合用来处理多个线程有共享数据的情况
联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

利用实现callable接口方式创建线程 第三种:
与使用runnable方式相比,callable功能更强大些:
runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果

class NumThread implements Callable{

    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <=100 ; i++) {
            if(i % 2 ==0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}
public class ThreadTest2{
    public static void main(String[] args) {
        NumThread numThread = new NumThread();
   //将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);

        new Thread(futureTask).start();
  //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
        try {
          Object  sum = futureTask.get();
            System.out.println("总共为:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

利用线程池方式创建线程 第四种:
背景
经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路
提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。
好处
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
创建线程池有哪几种方式
①. newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
②. newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
③. newSingleThreadExecutor()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
④. newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

class NumberThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            if(i % 2 ==0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            if(i % 2 !=0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
public class ThreadPool{
    public static void main(String[] args) {
//      提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
//      执行指定的线程的操作。需要提供实现Runnable接口或者Callable接口实现类的对象
//        service.execute();//适合适用于Runnable
        service.execute(new NumberThread());
        service.execute(new NumberThread1());
//        service.submit();//适合适用于Callable
        service.shutdown();
    }

}

5.线程的相关API

获取线程名 :
Thread.currentThread().getName()
1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活

6.线程相关例子

车站卖票:
/**
	 *
	 * 创建三个窗口卖票,总票数为100张,使用继承自Thread方式
	 * 用静态变量保证三个线程的数据独一份
	 * 
	 * 存在线程的安全问题,有待解决
	 *
	 * */
class window extends Thread{
  // private int ticket = 100;
	 private static int ticket = 100;
    @Override
    public void run() {
        while (true){
            if(ticket>0){
                System.out.println(getName() + ":买票,票号为:"+ticket);
            }else {
                break;
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        window ti = new window();
        window t2 = new window();
        window t3 = new window();

        ti.setName("窗口1");
        ti.setName("窗口2");
        ti.setName("窗口3");

        ti.start();
        t2.start();
        t3.start();
    }
}

7.线程安全问题:

什么是线程安全问题呢?
线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

方法一:同步代码块
Synchronized(同步锁)
Synchronized(同步监视器){
//需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:多个线程操作的变量。比如:ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象都可以充当锁。
要求:多个线程必须要共用一把锁。

class window1 implements Runnable{
    private int ticket = 100;
    Object obj = new Object();
    @Override
    public void run() {
        while(true){
            synchronized (obj){
            //synchronized (windows1.class){ 类也是对象
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }
}

public class ThreadTest3 {
    public static void main(String[] args) {
        window1 w = new window1();
        //虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票
        Thread t1=new Thread(w);
        Thread t2=new Thread(w);
        Thread t3=new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

方法二:同步方法

class window3 implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while(true){
            show();
    }
}
    private  synchronized  void show(){
        if(ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":买票,票号为:"+ticket);
            ticket--;
        }
    }
}
public class windowTest3 {
    public static void main(String[] args) {
        window3 w = new window3();
        //虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票
        Thread t1=new Thread(w);
        Thread t2=new Thread(w);
        Thread t3=new Thread(w);
        
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        
        t1.start();
        t2.start();
        t3.start();
    }
}

方式三:lock锁方法

class windows implements Runnable{

    private int ticket = 100;
//  1.实例化ReetrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                //2.调用lock()
                lock.lock();

                if (ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+":售票,票号"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }finally {
//                调用lock解锁
                lock.unlock();
            }
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        windows w = new windows();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }

}

** 面试题 :synchronize 与 Lock的异同?**
== 相同:二者都是解决线程安全问题==
== 不同:synchronize 机制自动在执行完相应的同步代码以后,自动的释放同步监视器==
Lock需要手动的启用同步( Lock() ),同时结束同步也需要手动的实现(unLock())
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)

8.什么是死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。产生死锁的原因,主要包括:

系统资源不足;
程序执行的顺序有问题;
资源分配不当等。

8.1死锁的代码
public class DeadLockDemo implements Runnable{
 
    public static int flag = 1;
 
    //static 变量是 类对象共享的
    static Object o1 = new Object();
    static Object o2 = new Object();
 
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":此时 flag = " + flag);
        if(flag == 1){
            synchronized (o1){
                try {
                    System.out.println("我是" + Thread.currentThread().getName() + "锁住 o1");
                    Thread.sleep(3000);
                    System.out.println(Thread.currentThread().getName() + "醒来->准备获取 o2");
                }catch (Exception e){
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName() + "拿到 o2");//第24行
                }
            }
        }
        if(flag == 0){
            synchronized (o2){
                try {
                    System.out.println("我是" + Thread.currentThread().getName() + "锁住 o2");
                    Thread.sleep(3000);
                    System.out.println(Thread.currentThread().getName() + "醒来->准备获取 o2");
                }catch (Exception e){
                    e.printStackTrace();
                }
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName() + "拿到 o1");//第38行
                }
            }
        }
    }
 
    public static  void main(String args[]){
 
        DeadLockDemo t1 = new DeadLockDemo();
        DeadLockDemo t2 = new DeadLockDemo();
        t1.flag = 1;
        new Thread(t1).start();
 
        //让main线程休眠1秒钟,保证t2开启锁住o2.进入死锁
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        t2.flag = 0;
        new Thread(t2).start();
 
    }
}

代码中, t1创建,t1先拿到o1的锁,开始休眠3秒。然后 t2线程创建,t2拿到o2的锁,开始休眠3秒。然后 t1先醒来,准备拿o2的锁,发现o2已经加锁,只能等待o2的锁释放。 t2后醒来,准备拿o1的锁,发现o1已经加锁,只能等待o1的锁释放。 t1,t2形成死锁。

在这里插入图片描述

8.2解决办法

死锁一旦发生,我们就无法解决了。所以我们只能避免死锁的发生。 既然死锁需要满足四种条件,那我们就从条件下手,只要打破任意规则即可。

1.(互斥)尽量少用互斥锁,能加读锁,不加写锁。当然这条无法避免。
2. (请求和保持)采用资源静态分配策略(进程资源静态分配方式是指一个进程在建立时就分 配了它需要的全部资源).我们尽量不让线程同时去请求多个锁,或者在拥有一个锁又请求不到下个锁时,不保持等待,先释放资源等待一段时间在重新请求。
3. (不剥夺)允许进程剥夺使用其他进程占有的资源。优先级。
4. (循环等待)尽量调整获得锁的顺序,不发生嵌套资源请求。加入超时。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值