java多线程相关问题

作者:Director of Chatgpt

有关java中多线程的记录

Java多线程的学习已经暂时结束,记录一下相关重要的内容

普通的main方法下的执行顺序只有单一的线程,无法调用多线程cpu的性能,这时我们可以手动创建多个个线程或者使用线程池。

以下是使用线程池的基本方法

  1. 创建一个Thread类的后代类,注意是直接的后代类,并且重写run方法。

然后创建其对象并且调用其start方法进行启动。(或者使用匿名子类的方式进行创建,本质上相同。)

示例:

class  MyThread extends Thread{
    @Override
    public void run() {//将此线程要做的事情写在这里
        for(int i=0;i<100;i++){
            if(i%2==0){
                System.out.println(i);
            }
        }

    }
}//此类中的方法和main中的是两个线程
public class test1{
    public static void main(String[] args) {

        MyThread x1=new MyThread();
        x1.start();//启动当前线程,并且调用run方法
        System.out.println("hellow");
        //tip:不能直接调用否则就不是多线程,且此方法只能用一次,想要建立其他线程,可以建立其他对象。
    }







创建一个实现了runnable接口的A类,并且实现其中的runnable方法,创建类的对象,并且额外创建一个thread类的对象,并且将A类的对象放入thread类的构造器中,再次调用start方法。

示例:class Mthread 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 study1 {
    public static void main(String[] args) {
        Mthread x1=new Mthread();
        Thread t1 = new Thread(x1);//多态
        t1.setName("线程1");
        t1.start();//无论什么方法都要调用thread中的start方法
        //再次启动一个线程
        Thread t2=new Thread(x1);
        t2.setName("线程2");
        t2.start();
    }

} 

 

  1. 重写callable接口发方式:【(比较强大)如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

* 1. call()可以有返回值的。

* 2. call()可以抛出异常,被外面的操作捕获,获取异常的信息

* 3. Callable是支持泛型的】

创建步骤(以代码形式):

class 实现callable implements Callable{

    //2.实现call方法,将此线程需要执行的操作声明在call()中

    @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 ThreadNew {

    public static void main(String[] args) {

        //3.创建Callable接口实现类的对象

        实现callable numThread = new 实现callable();

        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象

        FutureTask futureTask = new FutureTask(numThread);

        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()

        new Thread(futureTask).start();




        try {

            //6.获取Callable中call方法的返回值

            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。

            Object sum = futureTask.get();

            System.out.println("总和为:" + sum);

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

    }




}

 

  1. 使用线程池的方式进行:

好处与专有方法:

1.提高响应速度(减少了创建新线程的时间)

* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)

* 3.便于线程管理

*      corePoolSize:核心池的大小

*      maximumPoolSize:最大线程数

*      keepAliveTime:线程没有任务时最多保持多长时间后会终止

*

创建实例:

lass 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) {

        //1. 提供指定线程数量的线程池

        ExecutorService service = Executors.newFixedThreadPool(10);

        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;

        //设置线程池的属性

//        System.out.println(service.getClass());

//        service1.setCorePoolSize(15);

//        service1.setKeepAliveTime();







        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象

        service.execute(new NumberThread());//适合适用于Runnable

        service.execute(new NumberThread1());//适合适用于Runnable




//        service.submit(Callable callable);//适合使用于Callable

        //3.关闭连接池

        service.shutdown();

    }




}

 

为了操控多线程的一些相关设置

Java在父类中提供了一些方法:

测试Thread中的常用方法:

* 1. start():启动当前线程;调用当前线程的run()

* 2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中

* 3. currentThread():静态方法,返回执行当前代码的线程

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

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

* 6. yield():释放当前cpu的执行权

* 7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。

* 8. stop():已过时。当执行此方法时,强制结束当前线程。

* 9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前

*                          线程是阻塞状态。

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

*

*

* 线程的优先级:

* 1.

* MAX_PRIORITY:10

* MIN _PRIORITY:1

* NORM_PRIORITY:5  -->默认优先级

* 2.如何获取和设置当前线程的优先级:

*   getPriority():获取线程的优先级

*   setPriority(int p):设置线程的优先级

*

*   说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下

*   被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。

*

*

 

 

 

 

以下是多线程一些专有名词:

进程是程序一次执行过程中或者是正在运行的一种,有时间,或者叫做生命周期,

特性:可以同时工作

main方法代表一个线程

一个进程会有多个线程

都有一套jvm内存结构

多个线程共享方法区和堆

每个进程分配不同的内存区域

进程是资源分配的单位

一个进程并行多个线程就是支持多线程

线程拥有独立的运行栈和程序计数器,线程切换的开销小。

一个进程的多个线程关共享相同的内存空间,从同一堆中分配对象可以访问相同的变量和对象。这使得线程之间的通信更简便,高效。但多个线程操作共享的系统可能会

带来安全隐患

线程的同步可以解决线程的安全问题。

以前的单核cpu其实是假的多线程,只不过cpu速度快造成的假象。

多核的cpu才是真正的多线程

cpu的主频跟速度有很大关系

java应用程序至少有三个线程,main()主线程,gc()垃圾回收线程,异常处理

并行与并发

并行:多个cpu同时执行多个任务

并发:一个cpu同时执行多个任务。比如秒杀,多个人做同一件事情。

多线程的优点:

(单核cpu使用单线程的模式,比多线程的模式快。

多线程的cpu切换会消耗时间。)

1.多线程程序的优点:

提高程序的响应。对图形化页面更有意义,可以增强用户体验

2.提高计算机系统cpu的利用率

改善程序结构。将即长又复杂的进程分为多个线程,独立运行,利于理解和修改。

创建多线程的条件:

同时要运行两个任务。

要暂停等待输入等操作的程序

需要一些跑在后台的程序

 

多线程的创建提高了效率但是在有些方面我们只能是使用单线程,比如:

系统的买票卖票:如果继续使用多线程则会导致许多问题,但是如果因为一点小的问题就放弃多线程的使用得不偿失,java中给了两全其美的方法,线程锁的出现就像是给关键的地方上了一把锁,让多个线程进行争夺,只有一个线程可以优先进入,而谁的速度块就可以先进去,别的线程想进入只有等待前面一个线程出来,就像是厕所的包间一样。有两种常用的线程锁

  1. synchronized(同步监视器):

用法示例:方式一:同步代码块

*

*   synchronized(同步监视器){

*      //需要被同步的代码

*

*   }

*  说明:1.操作共享数据的代码,即为需要被同步的代码。  -->不能包含代码多了,也不能包含代码少了。

*       2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。

*       3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。

*          要求:多个线程必须要共用同一把锁。

*

*       补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。在继承Thread类时打多用类名.class充当。

*  方式二:同步方法。

*     如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。

*同步监视器的解析,就是一个对象,此对象要具有多个线程只加载一次的性质(唯一性),判断线程是否抢到“厕所”的标志就是看这个对象所属,相同的看某个线程是否被释放,就是看此对象是否被释放。

2.lock锁的方式进行。

synchronized 与 Lock的异同?

*   相同:二者都可以解决线程安全问题

*   不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器

*        Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())

*

* 优先使用顺序:

* Lock  同步代码块(已经进入了方法体,分配了相应资源)  同步方法(在方法体之外)

*使用实例:

class Window implements Runnable{




    private int ticket = 100;

    //1.实例化ReentrantLock

    private ReentrantLock lock = new ReentrantLock(true);




    @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 {

                //3.调用解锁方法:unlock()

                lock.unlock();

            }




        }

    }

}




public class LockTest {

    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();

    }

}





















 

同样的事情没有完美的,什么事情都有两面性:线程锁出现的同时也会带来死锁问

题,以下就是标准的错误示范:

package com.atguigu.java1;

//死锁的演示

class A {

public synchronized void foo(B b) { //同步监视器:A类的对象:a

        System.out.println("当前线程名: " + Thread.currentThread().getName()

                      + " 进入了A实例的foo方法"); // ①

        try {

               Thread.sleep(200);

        } catch (InterruptedException ex) {

               ex.printStackTrace();

}

        System.out.println("当前线程名: " + Thread.currentThread().getName()

                      + " 企图调用B实例的last方法"); // ③

        b.last();

}




public synchronized void last() {//同步监视器:A类的对象:a

        System.out.println("进入了A类的last方法内部");

}

}




class B {

public synchronized void bar(A a) {//同步监视器:b

        System.out.println("当前线程名: " + Thread.currentThread().getName()

                      + " 进入了B实例的bar方法"); // ②

try {

               Thread.sleep(200);

        } catch (InterruptedException ex) {

               ex.printStackTrace();

        }

       System.out.println("当前线程名: " + Thread.currentThread().getName()

                      + " 企图调用A实例的last方法"); // ④

        a.last();

}




public synchronized void last() {//同步监视器:b

        System.out.println("进入了B类的last方法内部");

}

}




public class DeadLock implements Runnable {

A a = new A();

B b = new B();




public void init() {

        Thread.currentThread().setName("主线程");

        // 调用a对象的foo方法

        a.foo(b);

        System.out.println("进入了主线程之后");

}




public void run() {

        Thread.currentThread().setName("副线程");

        // 调用b对象的bar方法

        b.bar(a);

        System.out.println("进入了副线程之后");

}




public static void main(String[] args) {

        DeadLock dl = new DeadLock();

        new Thread(dl).start();







        dl.init();

}

}

 

此段代码会停止运行,因为两个线程同时分别安装了两个线程同步器,在进行到第二个同步监视器时发现所需的对象锁恰好是第一层的锁,并且因为没有执行完的原因两边都不肯放弃对象锁,这就导致两边都无法出来。

解决方法:

  1. 尽量不要使用嵌套的同步监视器。
  2. Lock锁可以很大程度上避免这个问题。
  3. 独立分配专门的对象锁。