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的一个普通方法调用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值