一、多线程实现方式

1.继承Thread类,重写run方法

       1)定义Thread类的子类,并重写Thread类的run()方法。
  2)创建Thread子类的实例,及创建了线程对象。
  3)调用线程对象的start()方法来启动该线程。
public class ThreadDemo extends Thread{
@Override public void run(){ //编写自己的线程代码 System.out.println(Thread.currentThread().getName()); } public static void main(String[] args){ ThreadDemo threadDemo = new ThreadDemo(); threadDemo.setName("我是自定义的线程1"); threadDemo.start(); System.out.println(Thread.currentThread().toString()); } }

 优点 :代码简单 。 缺点 :该类无法集成别的类。 

2.实现Runnable接口,重写run方法

通过实现Runnable接口,实现run方法,接口的实现类的实例作为Thread的target作为参数传入带参的Thread构造函数,通过调用start()方法启动线程

 

public class MyThreadRunable implements Runnable{
    public static void main(String[] arg){
        MyThreadRunable runable = new MyThreadRunable();
        Thread myThread = new Thread(runable);
        myThread.start();
        System.out.println("step2");

    }

    @Override
    public void run() {
        System.out.println("step1");
        try {
            sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

  

 

优点 :继承其他类。 同一实现该接口的实例可以共享资源。

缺点 :代码复杂

3.通过Callable和FutureTask创建线程

   1)创建Callable 接口的实现类,并实现 call() 方法,该  call()  方法将作为线程执行体,且该  call()  方法有返回值 。
        2)创建Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象, 该 FutrueTask 对象封装了该 Callable 对象的 call() 方法的返回值。
        3)使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。

public class ThreadDemo {

    public static void main(String[] args) {
       
        Callable<Object> oneCallable = new Tickets<Object>();
        FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable);

        Thread t = new Thread(oneTask);

        System.out.println(Thread.currentThread().getName());

        t.start();

    }

}

class Tickets<Object> implements Callable<Object>{

    //重写call方法
    @Override
    public Object call() throws Exception {
        System.out.println(Thread.currentThread().getName());
        return null;
    }   
}

优点 :可以获得返回值

start() : 作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用,真正的实现了多线程并发运行。
run() : 只是类的一个普通方法而已,直接调用run方法的话,程序中依然只有主线程这一个线程,其程序执行路径还是要顺序执行,要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的。

二、线程中的状态

java中,每个线程都需经历新生、就绪、运行、阻塞和死亡五种状态,线程从新生到死亡的状态变化称为生命周期。

用new运算符和Thread类或其子类建立一个线程对象后,该线程就处于新生状态。
新生—>就绪:通过调用start()方法。
就绪—>运行:处于就绪状态的线程一旦得到CPU,就进入运行状态并自动调用run()方法。
运行—>阻塞:处于运行状态的线程,执行sleep()方法,或等待I/O设备资源,让出CPU并暂时中止运行,进入阻塞状态。
阻塞—>就绪:睡眠时间已到,或等待的I/O设备空闲下来,线程便进入就绪状态,重新到就绪队列中等待CPU。当再次获得CPU时,便从原来中止位置开始继续运行。
运行—>死亡:(1)线程任务完成;(2)线程被强制性的中止,如通过执行stop()或destroy()方法来终止线程。

 三、如何优雅的终止一个线程

线程终止有两种情况:线程的任务执行完成;线程在执行任务过程中发生异常。

 1、使用stop()方法,已被弃用。原因是:stop()是立即终止,比较暴力,会带来数据不一致性,所以被废弃。
 2、使用退出标志:实现一个Runnable接口,在其中定义volatile标志位,在run()方法中使用标志位控制程序运行,当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。
 3、使用interrupt()中断的方式,使用interrupt()方法中断正在运行中的线程只会修改中断状态位,可以通过isInterrupted()判断。如果使用interrupt()方法中断阻塞中的线程,那么就会抛出InterruptedException异常,可以通过catch捕获异常,然后进行处理后终止线程。有些情况,我们不能判断线程的状态,所以使用interrupt()方法时一定要慎重考虑。

四、线程中sleep()和wait()有何区别

sleep():使当前线程暂停执行指定的一段时间,但监视状态依然保持,过了指定的时间会自行恢复运行状态。

wait():使当前线程暂停执行,同时释放对象监视器的所有权,直到另一个和它有相同对象监视器的线程调用notify()或者notifyAll()唤醒它,再恢复运行状态。

区别: 

(1)sleep()不会释放资源,wait()会释放资源;

(2)sleep()是Thread类里的函数,wait()是Object类里的函数;

(3)sleep()可以在任何地方调用,wait()只能在同步方法或者同步代码块中调用(否则会抛IllegalMonitorStateException异常);

五、Runnable接口和Callable接口的区别

Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

六、多线程同步

(1)synchronized关键字:synchronized有两种用法:synchronized方法和synchronized块
    public synchronized void mutithreadAccess();
    synchronized(syncObject){代码块} 

 

  当一个线程进入一个对象的synchronized()方法后,其他线程是否能够进入此对象的其他方法?
               答案:其他线程可进入此对象的非synchronized修饰的方法。如果其他方法有synchronized修饰,都用的是同一对象锁,就不能访问。

 

   如果其他方法是静态方法,且被synchronized修饰,是否可以访问?
                 答案:可以的,因为static修饰的方法,它用的锁是当前类的字节码,而非静态方法使用的是this,因此可以调用。

 

(2)Thread类的join()方法

  join()方法是Thread中的一个public方法,它有几个重载版本:

    1. join()

    2. join(long millis)    //参数为毫秒

    3. join(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

(3)wait()方法和notify()方法:当使用synchronized来修饰某个共享资源时,如果线程A1执行synchronized代码,另外一个线程A2也要同时执行同一对象的同一                   synchronized代码时,线程A2将要等到线程A1执行完后,才能继续执行。这种情况下可以使用wait()方法和notify()方法。
  在synchronized代码被执行期间,线程可以调用对象的wait方法,释放对象锁,进入等待状态,并且可以调用notify()方法或notify()方法通知正在等待的其他线           程。notify()方法仅唤醒一个线程(等待队列中的第一个线程)并允许它去获得锁,notifyAll()方法唤醒所有等待这个对象的线程并允许它们去获得锁。
(4)Lock
  ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力
  ReenreantLock类的常用方法有:
    1. ReentrantLock() : 创建一个ReentrantLock实例
    2. lock() : 获得锁
    3.unlock() : 释放锁

 七、一些编程题

1、编写一个有两个线程的程序,第一个线程用来计算2~100000之间的素数的个数,第二个线程用来计算100000~200000之间的素数的个数,最后输出结果。

package classtest;

public class ThreadSushu extends Thread{
    int i,j,x=0;
    ThreadSushu (int m,int n){
        this.i=m;
        this.j=n;
    }
    public static void main(String[] args) {
        ThreadSushu thread1=new ThreadSushu(2,100000);
        ThreadSushu thread2=new ThreadSushu(100000,200000);
        thread1.start();
        thread2.start();
    }

    public void run(){
        int p,q;
        p=0;q=0;

        for(int m=i;m<=j;m++) {
            for(int h=1;h<=m;h++) {
                q=m%h;
                if(q==0)p=p+1;
            }
            if(p==2) {
                x=x+1;
            }
            p=0;
        }

        System.out.println("输出"+i+"到"+j+"之间的质数个数:"+x);
    }

}

2、设计2个线程,定义一个变量j,初始值为0。其中第1个线程每个循环对j增加1,循环1000次。另外1个线程对j每次减少1,循环1000次。保证线程安全,最终两个线程执行完j的值仍然是0

package classtest;

public class  Thread2 extends Thread{
    static  int j=0;
     static Object lock=new Object();
    int flag=0;
    Thread2(int flag){
       this.flag=flag;
    }
    public static void main(String[] args) throws InterruptedException {
        Thread2 thread1=new Thread2(1);
        Thread2 thread2=new Thread2(2);
        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
        System.out.println(j);
    }

    public void run(){
        for(int i=0;i<100000;i++) {
            synchronized(lock){
            if (flag == 1) {
                j=j+2;
            } else {
                j=j-1;
            }
         }
       }
    }
}

3、假如新建T1、T2、T3三个线程,同时启动,如何保证它们按顺序执行?也就是先执行T1,执行完后,再执行T2,执行完后,再执行 T3

package classtest;

public class JoinTest {
    public static void main(String[] args) {
        Thread t1=new Thread(new Work(null));
        Thread t2=new Thread(new Work(t1));
        Thread t3=new Thread(new Work(t2));
        t1.start();
        t2.start();
        t3.start();
    }
    static class Work implements Runnable {
        private Thread t;

        public Work(Thread t) {
            this.t = t;
        }

        public void run() {
            if (t != null) {
                try {
                    t.join();
                    System.out.println("thread start:"+Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                System.out.println("thread start:" + Thread.currentThread().getName());
            }
        }
    }
}

4、写两个线程,一个线程打印1~52,另一个线程打印A~Z,打印顺序是12A34B...5152Z;

package classtest;

public class TwoThread {
    public static void main(String args[]){
        MyObject1 my = new MyObject1();
        new Thread(new Runnable(){

            @Override
            public void run() {
                // TODO Auto-generated method stub
                for(int i = 0; i < 26; i++){
                    my.printNum();
                }
            }

        }).start();
        new Thread(new Runnable(){

            @Override
            public void run() {
                // TODO Auto-generated method stub
                for(int i = 0; i < 26; i++){
                    my.printA();
                }
            }

        }).start();
    }
}
class MyObject1{
    private static boolean flag = true ;
    public  int count = 1;

    public synchronized void printNum(){
        while(flag == false){
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.print((2*count-1));
        System.out.print(2*count);

        flag = false;
        this.notify();
    }
    public synchronized void printA(){
        while(flag == true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.print((char)(count+'A'-1));
        count++;
        flag = true;
        this.notify();
    }
}

5、编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加。

package classtest;

public class TenThread {
    public static class SumThread extends Thread{

        int forct = 0;  int sum = 0;

        SumThread(int forct){
            this.forct = forct;
        }

        @Override
        public void run() {
            for(int i = 1; i <= 10; i++){
                sum += i + forct * 10;
            }
            System.out.println(getName() + "  " + sum);
        }
    }

    public static void main(String[] args) {

        int result = 0;

        for(int i = 0; i < 10; i++){
            SumThread sumThread = new SumThread(i);
            sumThread.start();
            try {
                sumThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            result = result + sumThread.sum;
        }
        System.out.println("result   " + result);
    }
}