多线程与线程安全1

一、基本概念

1、程序:是为了完成特定的任务,用某种语言编写的一组指令的集合。一段静态的代码。

2、进程:程序的一次执行过程,或是正在运行的程序。是一个动态的过程,自身的产生、存在和消亡。(生命周期)

3、线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程的。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器。线程的切换开销小。一个进程中的多个线程共享相同的内存单元,可以访问相同的变量和对象,每个线程都拥有自己的栈空间,共有同一个堆内存,使得线程间的通信更加的简便高效。但多线程共享系统资源可能会带来安全隐患

3、单核cpu,其实就是一种假的线程,同一个时间内只能执行一个线程。例如收费站,有多个通道只有一个收费员。但是切换的频率高,感觉不出来。多核cpu,就是多线程。收费站,有多个通道和多个一个收费员。

4、并行:两个或多个事件在同一时刻发生

5、并发:两个或多个事件在同一时间段发生

6、使用多线程的优点:

提高应用程序的响应,对图形化界面更有意义。

提高cpu的利用率。

改善程序结构,将长而复杂的程序分为多个线程,独立运行。

7、为何需要多线程:

程序需要同时执行多个任务。

程序需要实现一些需要等待的任务时,如用户的输入、文件读写操作等。

需要后台运行程序时。

二、线程的创建和使用

1、线程的创建(两种方式)和启动:

Java的JVM允许程序运行多个线程,通过java.lang.Thread类来实现

Thread类的特性:每个线程都是通过某个特定Thread对象的run()方法来完成操作,run()方法的主体称为线程体.

通过该Thread对象的start() 方法来启动线程,而非直接调用run()方法。

2、Thread类中常用的方法:

stat():启动当前线程

run():重写该方法,将要执行的操作声明在方法中

currentThread():静态方法,返回当前的线程

getName():获取当前线程的名字

setName():设置当前线程的名字

yield():释放当前cpu的执行权,让其他的先走一会

join():线程A调用调用线程B的join(),让线程A进入阻塞状态,直到线程B执行完,才执行A

sleep(long millitime):让当前线程睡眠millitime时间

isAlive():判断当前线程是否还存活.

3、线程的调度,两种策略:

时间片:一段时间执行A,一段时间执行B

抢占式:优先级高的线程抢占CPU

4、线程的优先级:

MAX_PRIORITY:10

MIN_PRIORITY:1

NORM_PRIORITY:5(默认)

通过getPriority() 方法来获取当前线程的优先级,通过setPriority() 方法来设置优先级

注意:高优先级要抢占低优先级线程cpu的执行权,只是在概率上来讲,并不意外着高优先级执行完才是低优先级.

5、创建线程的第一种方式:

创建一个继承于Thread的子类,重写run方法(将执行操作写在里面),创建Thread对象然后调用start()方法

public class Main {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
        System.out.println("111");//与上面的那个同时进行
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i%2 == 0) System.out.println(i);
        }
    }
}
public class Main {
    public static void main(String[] args) {
        //匿名子类创建,同时进行两个线程
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if(i%2 == 0) System.out.println(i);
                }
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if(i%2 != 0) System.out.println(i);
                }
            }
        }.start();

    }
}

6、创建线程的第二种方式:

创建一个实现Runnable的接口,实现类去实现Runnable中的抽象方法run,创建实现类的对象,将此对象作为形参传到Thread类的构造器中,创建Thread对象,然后调用start方法。(Thread也是Runnable接口的实现类)

public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread);
        t1.setName("线程1");
        t1.start();
        // 再启动一个线程
        Thread t2 = new Thread(myThread);
        t2.setName("线程2");
        t2.start();

    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i%2==0) System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

实现Runnable接口的优势是:

1、通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程的同时执行相同的任务情况。

2、可以避免单继承的局限性。

3、任务与线程本身是分离的,提高了程序的健壮性。

4、后继学习的线程池技术,只接收实现Runnable接口的任务。

小练习:

方式一:写三个窗口卖一百种票
public class Main {
    public static void main(String[] args) {
        window w1 = new window();
        window w2 = new window();
        window w3 = new window();
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");
        w1.start();
        w2.start();
        w3.start();

    }
}

class window extends Thread{
    private static int ticket = 100;//三个窗口总共卖100张,用静态属性

    @Override
    public void run() {
        while (true){
            if( ticket > 0){
                System.out.println(Thread.currentThread().getName() + "卖出票:"+ticket);
                ticket--;
              }else{
                break;
            }
            ticket--;
        }
    }
}


方式二:写三个窗口卖一百种票
public class Main {
    public static void main(String[] args) {
        window w = new window();
        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 window implements Runnable{
    private int ticket = 100;//不用设置成static,公用同一个对象
    @Override
    public void run() {
        while (true){
            if( ticket > 0){
                System.out.println(Thread.currentThread().getName() + "卖出票:"+ticket);
                ticket--;
            }else{
                break;
            }
        }
    }
}

有线程安全的问题:比如会出现重票、错票等

7、中断线程

//主线程和t1线程同时执行任务,主线程先执行完后中断t1
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new MyThread());
        t1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+" :"+i);
            Thread.sleep(500);
        }
        //当主线程执行完后,中断t1
        t1.interrupt();//打个标记进入catch
    }

    static class MyThread implements Runnable{

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+" :"+i);
                try {
                    Thread.sleep(1000);//只能进行try-catch,父接口没有声明异常抛出,子就不能声明更大的
                } catch (InterruptedException e) {
                    //InterruptedException 线程中断异常,有wait、sleep、interrupt时会被捕获
                    //e.printStackTrace();
                    return;//结束线程,如果直接调用.stop 会导致一些应该被释放的资源没有被释放
                }
            }
        }
    }
}

8、守护线程

线程可以分为:

用户线程:当一个进程不包含任何存活的用户线程时,进程结束。

守护线程(Daemon):当最后一个用户线程结束时,所有守护线程自动死亡。

创建方法:

setDaemon(true),来设置线程为“守护线程”,其他非守护线程执行完后,该线程自动结束。

三、线程的安全性问题:

1、线程的安全性:

线程出现安全性的原因:

当多条语句在操作同一个线程共享数据时,一个线程对多条语句执行了部分,还没有执行完,另一个线程参与进来导致共享数据的错误。

解决办法:对多个操作共享数据的语句,只能让一个线程都执行完了才能执行其他线程。

2、解决方法一:同步代码块

synchronized (同步监视器/锁对象) {//每个线程进来时,都会判断这个锁对象是否上锁
	//需要被同步的代码
}

1、操作共享数据的代码,即为需要被同步的代码(不能多,不能少)

2、同步监视器,俗称“锁”。任何一个类的对象都可以作为锁。多线程必须要共用一把锁

继承的时候
class window extends Thread{
    private static int ticket = 100;
    private static Object obj = new Object();//设置为static,保证为同一把锁
    @Override
    public void run() {
        while (true){
            synchronized (obj) {
            	if( ticket > 0){
                    System.out.println(Thread.currentThread().getName() + "卖出票:"+ticket);
                    ticket--;
                  }else{
                    break;
                }
                ticket--;
            }
        }
    }
}

3、解决方法二:同步方法

1、仍然需要锁

2、非静态的同步方法,同步监视器是:this

3、静态的同步方法,同步监视器是:类本身

public void synchronized 方法名(){
    //方法的内容
}
在用继承的方式,要考虑方法是否需要加static

3、解决方法二:显式定义同步锁。

对比synchronized,需要手动解锁

class window implements Runnable{
    private int ticket = 100;
    //造一个lock对象(静态,保证相同)
    private static ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try{
                lock.lock();//锁住
                if( ticket > 0){
                    System.out.println(Thread.currentThread().getName() + "卖出票:"+ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally {
                lock.unlock();//开锁
            }
        }
    }
}

5、公平锁和非公平锁

公平锁:当要等待线程的时候,等待的线程会依次排队,先到先得。

非公平锁:线程不会排队,直接抢。

上面说的显示锁和隐式锁默认都是非公平锁,但是显示锁可以设置为公平锁,只需要在创建对象的时候,设置ReentrantLock lock = new ReentrantLock(true);

4、应用:解决单例模式中的懒汉式的线程安全问题:

class Bank{
    private Bank(){}
    private static Bank instance = null;
    public static Bank getInstance(){
        if( instance == null) instance = new Bank();
        return instance;
    }
}
可能有多个线程进入getInstance中,造成new多个Bank

同步方法:
class Bank{
    private Bank(){}
    private static Bank instance = null;
    public static synchronized Bank getInstance(){
        if( instance == null) instance = new Bank();
        return instance;
    }
}

同步代码块:
class Bank{
    private Bank(){}
    private static Bank instance = null;
    public static Bank getInstance(){
        //方式一:效率低
        synchronized (Bank.class) {
            if( instance == null) instance = new Bank();
            return instance;
        }
        //方式二:
        if(instance == null){
            synchronized (Bank.class) {
                if( instance == null) instance = new Bank();
                return instance;
            }
        }
        return instance;
    }
}

5、死锁:

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。

public class Main {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //在它阻塞的0.1s内,下面的那个拿着s2等着要s1,而这个线程拿着s1,等着要s2,这样就形成了死锁
                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){
                    s1.append("c");
                    s2.append("3");
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值