java多线程并发1

目录

进程

线程

进程和线程的关系

并发(Concurrent)

并行(Parallel)

线程的状态

死锁问题 


进程

       进程是指内存中运行的一个应用程序,是操作系统资源分配的基本单位,有独立的内存空间,至少有一个线程,可以同时运行多个线程。

比如java -jar启动一个java的进程,可以通过参数配置指定分配多少系统资源给这个进程。但是这个进程本身是不执行任何任务的,至少启动了两个线程:主线程和垃圾回收线程。我们的代码逻辑都是cpu调度线程执行的。

linux查看当前系统运行的进程

ps aux
 a:显示当前终端下的所有进程信息,包括其他用户的进程。
 u:使用以用户为主的格式输出进程信息。
 x:显示当前用户在所有终端下的进程。

java启动进程的两种方式

线程

        线程是操作系统进行运算和调度的最小单元,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

 linux查看当前系统运行的线程

ps -xH | grep pid

java启动线程的三种方式

1,继承Thread类,重写run()方法,调用线程类的start()方法。

 2,实现Runnable接口,实现run()方法。

匿名内部类是实现Runnable接口

3,实现Callable接口,实现call方法

进程和线程的关系

        通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。

并发(Concurrent)

指两个或多个事件在同一时间段内发生

在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

并行(Parallel)

指两个或多个事件在同一时刻点发生

在操作系统中是指,一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。

//查看cpu型号
[root@VM-16-2-centos /]# cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c
      1  Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz

//查看物理cpu个数
[root@VM-16-2-centos /]# cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
1

//查看每个cpu的核数
[root@VM-16-2-centos /]# cat /proc/cpuinfo| grep "cpu cores"| uniq
cpu cores	: 1

//查看逻辑处理单元个数
[root@VM-16-2-centos /]# cat /proc/cpuinfo| grep "processor"| wc -l
1

当系统只有一个逻辑处理单元时,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待CPU,JAVA虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配CPU的使用权。

当系统有一个以上逻辑处理单元时,则同一个进程里的两个线程是有可能并行执行的。两个线程分别使用一个逻辑处理单元互不抢占CPU资源,可以做到同一时刻并行运行。

总结来说,单核处理器多个线程只能并发运行,多核处理器多个线程并发并行都存在。

线程的状态

源码中的截图,线程状态共有6个,并且一个线程同一时刻只存在一种状态。

合理利用多线程执行任务,可以有效利用CPU,提升程序的处理效率。但是在多线程运行时也会发生安全问题。

最经典的案例就是卖火车票。

public class ThreadTest {
    public static void main(String[] args) {
        Huochepiao huochepiao = new Huochepiao();
        Thread conductor1 = new Thread(huochepiao, "售票员1");
        Thread conductor2 = new Thread(huochepiao, "售票员2");
        conductor1.start();
        conductor2.start();
    }
}

class Huochepiao extends Thread{
    private static int ticket = 100;

    @Override
    public void run(){
        while (true){
            if (ticket <= 0) {
                return;
            }
            System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
            ticket--;
        }
    }
}

运行结果如下,发现售票员1和售票员2都卖出了第一张票。 

 为什么出现这样的问题呐,就要看java的内存模型了。

Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问其他工作内存中的变量,线程间变量值的传递均需要在主内存来完成。

比如上述出现的售票员1和售票员2都卖出了第一张票,就是售票员1从主内存取到总票数为100,然后卖出第一张票,此时票数变为99了,但是这个信息没有同步到主内存,售票员2开始运行的时候又从主内存取值,拿到的还是100张票,就导致了卖出了两个第一张票。

要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

解决思路:

1,多线程之间不同享变量。

1,线程封闭
        保证变量只被一个线程使用,就不会出现线程安全问题。

上述代码我们改造一下:

public class ThreadTest {
    public static void main(String[] args) {
        Huochepiao huochepiao1 = new Huochepiao(100);
        Huochepiao huochepiao2 = new Huochepiao(100);
        Thread conductor1 = new Thread(huochepiao1, "售票员1");
        Thread conductor2 = new Thread(huochepiao2, "售票员2");
        conductor1.start();
        conductor2.start();
    }
}

class Huochepiao extends Thread{
    private static Integer ticket = 0;
    Huochepiao(Integer ticket){
        this.ticket = ticket;
    }
    @Override
    public void run(){
        while (true){
            if (ticket <= 0) {
                return;
            }
            System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
            ticket--;
        }
    }
}

可以看到即使我们实例化了两个Huochepiao类,仍然出现了多卖的情况。因为ticket是static修饰的,属于类所有只有一份,所有多个线程共享还是有线程安全问题。 

 接下来我们去掉static修饰符,再次测试

public class ThreadTest {
    public static void main(String[] args) {
        Huochepiao huochepiao1 = new Huochepiao(100);
        Huochepiao huochepiao2 = new Huochepiao(100);
        Thread conductor1 = new Thread(huochepiao1, "售票员1");
        Thread conductor2 = new Thread(huochepiao2, "售票员2");
        conductor1.start();
        conductor2.start();
    }
}

class Huochepiao extends Thread{
    private Integer ticket = 0;
    Huochepiao(Integer ticket){
        this.ticket = ticket;
    }
    @Override
    public void run(){
        while (true){
            if (ticket <= 0) {
                return;
            }
            System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
            ticket--;
        }
    }
}

可以看到两个线程即两个售票员自己卖自己的票,根本不会影响其他线程中的变量。

2,栈封闭
        栈封闭即使用局部变量。局部变量只会存在于本地方法栈中,不能被其他线程访问,因此也就不会出现并发问题。所以如果可以使用局部变量就优先使用局部变量。

public class ThreadTest {
    public static void main(String[] args) {
        Huochepiao huochepiao = new Huochepiao();
        Thread conductor1 = new Thread(huochepiao, "售票员1");
        Thread conductor2 = new Thread(huochepiao, "售票员2");
        conductor1.start();
        conductor2.start();
    }
}

class Huochepiao extends Thread{
    @Override
    public void run(){
        Integer ticket = 100;
        while (true){
            if (ticket <= 0) {
                return;
            }
            System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
            ticket--;
        }
    }
}

可以看到我们将ticket定义在run方法内部,此时ticket变量就处于栈封闭,run方法压栈再出栈局部变量ticket是不受其他线程影响的。
3,ThreadLocal封闭
ThreadLocal是Java提供的实现线程封闭的一种方式,ThreadLocal内部维护了一个Map,Map的key是各个线程,而Map的值就是要封闭的对象。每个线程中的对象都对应着Map中一个值,也就是ThreadLocal利用Map实现了对象的线程封闭。

public class ThreadTest {
    public static void main(String[] args) {
        Huochepiao huochepiao = new Huochepiao();
        Thread conductor1 = new Thread(huochepiao, "售票员1");
        Thread conductor2 = new Thread(huochepiao, "售票员2");
        conductor1.start();
        conductor2.start();
    }
}

class Huochepiao extends Thread{
    private static Integer tickets = 100;
    private static ThreadLocal<Integer> ticketThreadLocal = new ThreadLocal<>();

    @Override
    public void run(){
        ticketThreadLocal.set(tickets);
        Integer ticket = ticketThreadLocal.get();
        while (true){
            if (ticket <= 0) {
                return;
            }
            System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
            ticket--;
        }
    }
}

结果上也是各有100张票各卖各的。 

 2,多线程同步机制

1,同步代码块

public class ThreadTest {
    public static void main(String[] args) {
        Huochepiao huochepiao = new Huochepiao();
        Thread conductor1 = new Thread(huochepiao, "售票员1");
        Thread conductor2 = new Thread(huochepiao, "售票员2");
        conductor1.start();
        conductor2.start();
    }
}

class Huochepiao extends Thread{
    private static Integer ticket = 100;

    @Override
    public void run(){
        while (true) {
            synchronized (this) {
                if (ticket <= 0) {
                    return;
                }
                System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
                ticket--;
            }
        }
    }
}

2,同步方法

ublic class ThreadTest {
    public static void main(String[] args) {
        Huochepiao huochepiao = new Huochepiao();
        Thread conductor1 = new Thread(huochepiao, "售票员1");
        Thread conductor2 = new Thread(huochepiao, "售票员2");
        conductor1.start();
        conductor2.start();
    }
}

class Huochepiao extends Thread{
    private static Integer ticket = 100;

    @Override
    public void run(){
        ticketSell();
    }

    synchronized private void ticketSell() {
        while (true) {
            if (ticket <= 0) {
                return;
            }
            System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
            ticket--;
        }
    }
}

3,lock锁

public class ThreadTest {
    public static void main(String[] args) {
        Huochepiao huochepiao = new Huochepiao();
        Thread conductor1 = new Thread(huochepiao, "售票员1");
        Thread conductor2 = new Thread(huochepiao, "售票员2");
        conductor1.start();
        conductor2.start();
    }
}

class Huochepiao extends Thread{
    private static Integer ticket = 100;
    private final Lock lock = new ReentrantLock();

    @Override
    public void run(){
        lock.lock();
        try {
            while (true) {
                if (ticket <= 0) {
                    return;
                }
                System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
                ticket--;
            }
        }finally {
            lock.unlock();
        }
    }
}

3,使用线程安全的类或集合

 线程安全对象有Vector,HashTable,经过Collections.synchronizedCollection()方法包装的集合对象、ConcurentHashMap,ConcurentLinkedQueue,CopyOnWriteArrayList、BlockingQueue的实现类型、AtomicInteger,AtomicLong等。

public class ThreadTest {
    public static void main(String[] args) {
        Huochepiao huochepiao = new Huochepiao();
        Thread conductor1 = new Thread(huochepiao, "售票员1");
        Thread conductor2 = new Thread(huochepiao, "售票员2");
        conductor1.start();
        conductor2.start();
    }
}

class Huochepiao extends Thread{
    private static AtomicInteger ticket = new AtomicInteger(100);

    @Override
    public void run(){
        while (true) {
            if (ticket.get() <= 0) {
                return;
            }
            int decrementAndGet = ticket.getAndDecrement();
            System.out.println(currentThread().getName() + " 卖出了第 " + (100 - decrementAndGet + 1) + " 张票");
        }
    }
}

死锁问题 

加锁不对还容易导致死锁问题。

public class ThreadTest {
    public static void main(String[] args) {
        Huochepiao huochepiao = new Huochepiao();
        Huochepiao2 huochepiao2 = new Huochepiao2();
        Thread conductor1 = new Thread(huochepiao, "售票员1");
        Thread conductor2 = new Thread(huochepiao2, "售票员2");
        conductor1.start();
        conductor2.start();
    }
}

class Huochepiao extends Thread{
    private static Integer ticket = 100;

    @Override
    public void run(){
        while (true) {
            synchronized (Huochepiao2.class) {
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (Huochepiao.class) {
                    if (ticket <= 0) {
                        return;
                    }
                    System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
                    ticket--;
                }
            }
        }
    }
}

class Huochepiao2 extends Thread{
    private static Integer ticket = 100;

    @Override
    public void run(){
        while (true) {
            synchronized (Huochepiao.class) {
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (Huochepiao2.class) {
                    if (ticket <= 0) {
                        return;
                    }
                    System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
                    ticket--;
                }
            }
        }
    }
}

jps看一下进程号

jstack看一下这两线程为啥不动了,发现死锁了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值