Java中的多线程

Java中的多线程

线程基础

线程是程序运行的基本执行单元。当操作系统(不包括单线程的操作系统,如微软早期的DOS)在执行一个程序时,会在系统中建立一个进程,而在这个进程中,必须至少建立一个线程(这个线程被称为主线程)作为这个程序运行的入口点。因此,在操作系统中运行的任何程序都至少有一个主线程。

一个进程中可以有一个或多个线程,进程和进程之间不共享内存,也就是说系统中的进程是在各自独立的内存空间中运行的。而一个进程中的线程可以共享系统分派给这个进程的内存空间。

线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。

创建线程

使用Thread创建线程

Thread类的构造函数

public Thread();
public Thread(String name);
public Thread(Runnable target);
public Thread(ThreadGroup group, Runnable target);
public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize) ;
public Thread(ThreadGroup group, String name) ;
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name) ;

public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize, boolean inheritThreadLocals) ;

参数说明:

  • Runnable target:实现了Runnable接口的类的实例。
  • String name:线程的名字,此名字可以在建立Thread实例后通过Thread类的setName方法设置。如果不设置线程的名字,线程就使用默认的线程名:Thread-N,N是线程建立的顺序,是一个不重复的正整数。
  • ThreadGroup group:当前建立的线程所属的线程组。如果不指定线程组,所有的线程都被加到一个默认的线程组中。
  • long stackSize:线程栈的大小。

通过Thread类的start()方法来执行线程代码

实例

public class MyThread extends Thread {
    public void run(){
        System.out.println(this.getName());
    }      
    public static void main(String[] args) {

        Thread th1=new MyThread();
        Thread th2=new MyThread();
        th1.start();//启动线程
        th2.start();//启动线程
    }     
}

运行结果

Thread-0
Thread-1

使用Runnable接口创建线程

public class MyRunnable implements Runnable {
   
    public void run(){
        System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args) {

        MyRunnable rb1=new MyRunnable();
        MyRunnable rb2=new MyRunnable();
        Thread th1=new Thread(rb1);
        Thread th2=new Thread(rb2);
        th1.start();
        th2.start();     
    }     
}

运行结果

Thread-0
Thread-1

控制线程

使用join()方法

Thread类的join()方法,此方法的功能是使异步执行的线程变成同步执行。而使用join()方法后,直到这个线程退出,程序才会往下执行。

加入join()方法后的代码实例

public class MyThread extends Thread {
    
    public void run(){
        try{
            sleep(3000);
            System.out.println(this.getName());
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args){

        Thread th1=new MyThread();
        th1.start();
        th1.join();
        System.out.println("hello");    
    }     
}

运行结果

Thread-0
hello

即使在线程中设置了3秒的等待,加入了join方法后,也要等线程运行完了才运行join后面的程序。

没有加入join方法的代码示例

public class MyThread extends Thread {
    
    public void run(){
        try{
            sleep(3000);
            System.out.println(this.getName());

        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {

        Thread th1=new MyThread();
        th1.start();     
        System.out.println("hello");  
    }     
}

运行结果

hello
Thread-0

可以看出没有加入join()方法,线程和主线程异步进行。

总结:当需要线程中的返回值的时候,就可以加上join()方法,使得线程与主线程是同步进行的。

后台线程(守护线程)

有一种线程是在后台运行的,其任务是为其他的线程提供服务,这种线程被称为“后台线程(Daemon Thread)”,又称为“守护线程”或“精灵线程”。JVM的垃圾回收线程就是典型的后台线程,后台线程有一个非常明显的特征——如果所有的前台线程都死亡,后台线程会自动死亡。

实例
setDaemon()

public class MyThread extends Thread {
    
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println(this.getName()+" "+i);
        }
    }

    public static void main(String[] args)  {

        Thread th1=new MyThread();
        th1.setDaemon(true);//设置为后台进程
        th1.start();

        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }    
    }//main线程死亡     
}

运行结果

main 0
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
Thread-0 4
Thread-0 5
Thread-0 6
Thread-0 7
Thread-0 8
main 1
Thread-0 9
Thread-0 10
main 2
Thread-0 11
main 3
Thread-0 12
main 4
Thread-0 13
Thread-0 14
Thread-0 15
Thread-0 16
Thread-0 17
Thread-0 18
Thread-0 19
Thread-0 20
Thread-0 21
Thread-0 22
Thread-0 23
Thread-0 24
Thread-0 25
Thread-0 26
Thread-0 27
Thread-0 28
Thread-0 29

可以看出新建的线程是没有运行完的,当主线程main运行结束后,Thread-0也结束了没有继续运行。

睡眠线程

如果我们需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread类的静态sleep方法实现,方法sleep有如下两种重载的形式。

//让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,
public static native void sleep(long millis);
//让当前正在执行的线程暂停millis毫秒加nanos毫微秒,并进入阻塞状态
public static void sleep(long millis, int nanos);

实例

public class MyThread extends Thread {
    
    public void run(){
        for(int i=0;i<5;i++){
            System.out.println(this.getName()+" "+i);
        }
    }

    public static void main(String[] args)  throws InterruptedException{

        Thread th1=new MyThread();
        Thread th2=new MyThread(); 
        Thread th3=new MyThread(); 
        th1.start();       
        th2.start();
        Thread.sleep(100);//线程睡眠100毫秒
        th3.start();             
    }     
}

运行结果

Thread-1 0
Thread-0 0
Thread-1 1
Thread-0 1
Thread-1 2
Thread-0 2
Thread-1 3
Thread-0 3
Thread-1 4
Thread-0 4
Thread-2 0
Thread-2 1
Thread-2 2
Thread-2 3
Thread-2 4

可以看出线程Thread-0和Thread-1正常运行,线程Thread-2会等待200毫秒在运行。

线程同步

可以使用join()方法

实例

public class Demo {
    public static void main(String[] args)  throws Exception{
        Thread add=new AddThread();
        Thread de=new DeThread();
        add.start();
        add.join();
        de.start();
        de.join();
        System.out.println(Counter.count);   
    }     
}

class Counter{
    public static int count=0;
}

class AddThread extends Thread{
    public void run(){
        for(int i=0;i<10000;i++){
            Counter.count+=1;
        }
    }
}

class DeThread extends Thread{
    public void run(){
        for(int i=0;i<10000;i++){
            Counter.count-=1;
        }
    }
}

运行结果

0

假如没有使用join方法,最后的数据会是各种各样的,这样活导致数据不安全。

也可以使用synchronized进行加锁

为了一段代码的原子性,可以通过加锁和解锁的方式,格式如下:

synchronized(lock) {//加锁
...
}//解锁

实例

public class Demo {
    public static void main(String[] args)  throws Exception{
        Thread add=new AddThread();
        Thread de=new DeThread();
        add.start();
        de.start();
        de.join();
        System.out.println(Counter.count);        
     
    }     
}

class Counter{
    public static final Object lock = new Object();
    public static int count=0;
}

class AddThread extends Thread{
    public void run(){
        for(int i=0;i<10000;i++){
            synchronized(Counter.lock){
                Counter.count+=1;
            }        
        }
    }
}

class DeThread extends Thread{
    public void run(){
        for(int i=0;i<10000;i++){
            synchronized(Counter.lock){
                Counter.count-=1;
            }
        }
    }
}

运行结果

0

代码中加了de.join();只是为了让线程运行完了才打印,不然会出现其他的数字。

方法同步

为了增加线程的效率,可以使用synchronized对方法进行同步。

实例

public class Demo {
   
    public static void main(String[] args)  throws Exception{
        Counter c1=new Counter();
        Thread th1=new Thread(()->{c1.add();});
        Thread th2=new Thread(()->{c1.dec();});
        th1.start();
        th2.start();
        th2.join();
        System.out.println(Counter.count);       
     
    }     
}

class Counter{
    public static int count=0;
    
    public synchronized void add(){
         for(int i=0;i<10000;i++){    
            Counter.count+=1;   
            
        }
    }
    public synchronized void dec(){
        for(int i=0;i<10000;i++){    
            Counter.count-=1;             
        }
    }
}

运行结果

0

这样即使多个线程去操作同一个数也是数据安全的,通过合理的设计和数据封装可以让一个类变为“线程安全”。

总结

线程和函数的关系

任何一个线程在建立时都会执行一个函数,这个函数叫做线程执行函数。也可以将这个函数看做线程的入口点(类似于程序中的main函数)。无论使用什么语言或技术来建立线程,都必须执行这个函数(这个函数的表现形式可能不一样,但都会有一个这样的函数)。

在run方法中使用线程名时带来的问题

在调用start()方法前后都可以使用setName设置线程名,但在调用start()方法后使用setName修改线程名,会产生不确定性,也就是说可能在run()方法执行完后才会执行setName。如果在run()方法中要使用线程名,就会出现虽然调用了setName()方法,但线程名却未修改的现象,类Thread的start()方法不能多次调用,否则会抛出异常。

继承Thread类或实现Runnable接口方式的比较

当采用Runnable接口方式实现多线程时,线程类只是实现了Runnable接口,还可以继承其他类。

当采用继承Thread类方式的多线程时,劣势是因为线程类已经继承了Thread类,所以不能再继承其他父类;

start()和run()的区别

使用方法start()来启动线程
方法run()只是thread的一个普通方法调用

### 回答1: Java 多线程可以通过创建 Thread 类的实例来实现。具体步骤如下: 1. 创建一个实现 Runnable 接口的类,该类实现 run() 方法。 ```java public class MyRunnable implements Runnable { public void run() { // 执行线程的操作 } } ``` 2. 创建一个 Thread 实例,并将实现了 Runnable 接口的类作为构造函数的参数。 ```java MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); ``` 3. 启动线程。 ```java thread.start(); ``` 此外,还可以通过继承 Thread 类来实现多线程。具体步骤如下: 1. 创建一个继承自 Thread 类的类,重写 run() 方法。 ```java public class MyThread extends Thread { public void run() { // 执行线程的操作 } } ``` 2. 创建 MyThread 的实例。 ```java MyThread myThread = new MyThread(); ``` 3. 启动线程。 ```java myThread.start(); ``` 在多线程的使用,需要注意线程安全的问题,比如共享变量的访问、同步操作等。可以使用 synchronized 关键字或者 Lock 接口来保证线程安全。 ### 回答2: 在Java多线程的使用是指程序同时运行多个线程,每个线程执行自己的任务。Java多线程的使用可以带来以下几点好处: 1. 提高程序的效率:多线程可以对多个任务进行并发处理,提高程序的运行效率。例如,可以将网络请求和UI界面分别放在两个不同的线程,这样即使网络请求比较耗时,UI界面也能进行响应,不会出现界面假死的情况。 2. 充分利用系统资源:多线程可以充分利用系统的处理器资源,提高系统的利用率。在多核处理器上运行多个线程,可以让每个核心都得到充分利用,提高系统的整体性能。 3. 实现异步编程:多线程可以实现异步编程,即一个线程执行后续操作,不需要等待另一个线程的完成。这样可以提高程序的响应速度。例如,可以使用多线程来进行文件下载,下载过程可以同时进行其他操作。 4. 处理复杂的并发情况:在一些需要处理多个并发操作的场景多线程可以提供更好的解决方案。例如,在并发访问共享资源的情况下,使用线程锁可以保证对共享资源的安全访问,避免数据冲突和一致性问题。 Java使用多线程可以通过创建Thread类的实例或者实现Runnable接口来实现。通过继承Thread类来创建线程,需要重写run方法,在run方法定义线程要执行的任务。通过实现Runnable接口来创建线程,需要实现run方法,并将实现了Runnable接口的对象作为参数传递给Thread类的构造方法。 总之,Java多线程的使用使得程序可以同时执行多个任务,提高了程序的效率和用户体验,并且能够处理复杂的并发情况。但需要注意多线程的安全性和线程之间的协作,避免出现数据冲突和一致性问题。 ### 回答3: Java多线程的使用是指在一个程序同时执行多个任务或者同时处理多个请求。多线程可以提高程序的并发性和响应性,可以将耗时的操作和任务分配给不同的线程来执行,从而提高程序的运行效率。 在Java多线程的使用主要依靠Thread类或者实现Runnable接口来创建线程。可以通过继承Thread类创建一个线程类,并重写run方法,在run方法定义需要执行的任务;也可以实现Runnable接口,创建一个Runnable对象,然后将该对象作为参数传递给Thread类的构造方法,创建一个线程对象。 使用多线程的好处是可以充分利用处理器的多核特性,同时进行多个任务,提高程序的运行效率。多线程还可以提高程序的响应性,当程序有耗时的操作时,可以将其放在一个独立的线程执行,防止主线程被阻塞,提高用户体验。 然而,多线程的使用也存在一些问题。首先是线程安全问题,多个线程同时访问共享资源可能导致数据不一致或者数据损坏。为解决这个问题,可以使用同步机制,如synchronized关键字或Lock接口,保证在同一时间只有一个线程能够访问共享资源。其次,多线程的创建和销毁会消耗系统资源,如果线程数量过多,可能会影响系统性能。因此,在使用多线程时应该合理控制线程的数量。另外,线程之间的协调和通信也是一个值得关注的问题,可以使用wait、notify、join等方法来实现线程间的协作。 总之,Java多线程的使用可以提高程序的并发性和响应性,但需要注意线程安全、资源消耗以及线程协调和通信等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值