一、线程基础和线程之间的共享与协作

Thread常用方法

方法 说明
static int activeCount() 返回当前线程的线程组及其子组中活动线程数量的估计值。
void start() 使该线程开始执行;Java虚拟机调用这个线程的run方法。
static Thread currentThread() 获取当前执行线程引用
long getId() 获取线程id
String getName() 获取线程名
void setName(String name) 更改线程的名称。
int getPriority() 获取线程优先级值
int setPriority(int priority) 更改线程的优先级。
Thread.State getState() 获取线程状态
void interrupt() 中断这个线程
boolean isInterrupted() 查看线程是否被中断。
static boolean interrupted() 测试当前线程是否已被中断,调用这个方法会将中断标识恢复成false。
static void yield() 向调度程序提示,当前线程愿意放弃当前线程对处理器的使用。
void run() 线程运行时执行的会调用此方法
void sleep(long miliseconds) 使当前正在执行的线程休眠(暂时停止执行)达指定的毫秒数。
void join() 等待线程死亡。
void join(long miliseconds) 按指定的毫秒数等待线程死亡。
boolean isAlive() 测试线程是否处于活动状态。
void suspend() 用于挂起线程(已弃用)。
void resume() 用于恢复挂起的线程(已弃用)。
void stop() 用于停止线程(已弃用)。
boolean isDaemon() 测试该线程是否为守护线程。
void setDaemon(boolean b) 将线程标记为守护线程,需在调用start方法前设置。

启动线程的四种方式

继承Thread

class UseThread extends Thread {
    @Override
    public void run() {
        System.out.println("UseThread object run ...");
    }
    
    public static void main(String[] args) {
        UseThread useThread = new UseThread();
        useThread.start();
    }
}

实现Runnable

class UseRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("UseRunnable object run ...");
    }
    public static void main(String[] args) {
        UseRunnable useRunnable = new UseRunnable();
        Thread thread = new Thread(useRunnable);
        thread.start();
    }
}

实现Callable

class UseCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("UseCallable object call ...");
        return "call result";
    }

    public static void main(String[] args) {
        UseCallable callable = new UseCallable();
        FutureTask task = new FutureTask(callable);
        Thread thread = new Thread(task);
        thread.start();
        try {
            System.out.println("线程运行返回:" + task.get());
        } catch (InterruptedException | ExecutionException e) {
            task.cancel(true);//取消计算
            e.printStackTrace();
        }
    }
}

使用Executors

class NewThread {
    public static void main(String[] args) {
        Thread t = Executors.defaultThreadFactory().newThread(() -> System.out.println("使用Executors创建线程..."));
        t.start();
    }
}

使用实现Runnable的好处

  • 避免了单继承的局限性,一个类可以实现多个接口
  • 适合于资源的共享

以卖票为例,使用继承Thread:

class Ticket {
    private int ticketNum;

    public Ticket(int ticketNum) {
        this.ticketNum = ticketNum;
    }

    public void sell() {
        while (true) {
            synchronized (this) {
                if (ticketNum <= 0) break;
                ticketNum--;
                System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩[" + ticketNum + "]张票");
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class TicketThread extends Thread {
    private Ticket ticket;

    public TicketThread(Ticket ticket, String threadName) {
        super(threadName);
        this.ticket = ticket;
    }

    @Override
    public void run() {
        ticket.sell();
    }

    public static void main(String[] args) {
        //3个线程卖10张票
        Ticket ticket = new Ticket(10);
        new TicketThread(ticket, "线程1").start();
        new TicketThread(ticket, "线程2").start();
        new TicketThread(ticket, "线程3").start();
    }
}

使用实现Runnable实现:

class TicketRunnable implements Runnable {
    private int ticketNum;

    public TicketRunnable(int ticketNum) {
        this.ticketNum = ticketNum;
    }

    @Override
    public void run() {

        while (true) {
            synchronized (this) {
                if (ticketNum <= 0) break;
                ticketNum--;
                System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩[" + ticketNum + "]张票");
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        //3个线程卖10张票
        TicketRunnable ticket = new TicketRunnable(10);
        new Thread(ticket, "线程1").start();
        new Thread(ticket, "线程2").start();
        new Thread(ticket, "线程3").start();
    }
}

安全的停止线程方法

public class RunnableImpl implements Runnable {
    @Override
    public void run() {

        //当执行中断方法interrupt()后,线程并不会停止,而是会继续执行完,线程与线程之间是一种协作状态
//        while(true){
//            System.out.println("UseThread object run ...");
//        }

        //需要受到控制的话,使用isInterrupted()方法判断一下
//        while (!Thread.currentThread().isInterrupted()) {
//            System.out.println(Thread.currentThread().getName() + " is run ...");
//        }

        //使用interrupted()方法判断中断状态会将中断标识恢复成false
//        while (!Thread.currentThread().interrupted()) {
//            System.out.println(Thread.currentThread().getName() + " is run ...");
//        }

        while (!Thread.currentThread().isInterrupted()) {
            System.out.println(Thread.currentThread().getName() + " is run ...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                //抛出InterruptedException异常后中断标识会恢复成false
                //如果抛异常后需要中断,需要在调用interrupt()方法进行中断操作
                Thread.currentThread().interrupt();
            }
        }
        System.out.println(Thread.currentThread().getName() + " interrupt flag is:" + Thread.currentThread().isInterrupted());
    }

    public static void main(String[] args) throws InterruptedException {
        RunnableImpl runnable = new RunnableImpl();
        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(2000);
        t.interrupt();
    }
}

线程的停止由线程本身决定,使用线程的引用调用interrupt()方法只是向线程打个招呼。
线程的stop、resume、suspend方法不建议使用,stop方法可以导致线程不正确的释放资源,suspend方法容易导致线程死锁

守护线程

守护线程是同主线程一起死亡的线程,try中的finally块中的内容不一定会执行。

设置守护线程方法:在线程调用start()方法之前调用线程的void setDaemon(boolean on)方法,设置为ture,再调用start()方法,这个线程即为守护线程。

示例

/**
 * @CalssName DaemonThreadDemo
 * @Description 守护进程
 * @since JDK 1.8
 */
public class DaemonThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("I use Lambda expression...");
                }
            } finally {
                System.out.println("finally code...");
            }
        });
        t.setDaemon(true);
        t.start();
        Thread.sleep(50);
    }
}

线程优先级

设置线程优先级示例

/**
 * @CalssName ThreadPriority
 * @Description 线程优先级
 * @since JDK 1.8
 */
public class ThreadPriority {
    public static void main(String[] args) {

        Runnable runnable = new Runnable() {
            int t1count = 0, t2count = 0;

            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    synchronized (this) {
                        String tName = Thread.currentThread().getName();
                        if ("线程1".equals(tName)) {
                            t1count++;
                            System.out.println(tName + " run... " + t1count + "次");
                        }
                        if ("线程2".equals(tName)) {
                            t2count++;
                            System.out.println(tName + " run... " + t2count + "次");
                        }

                    }
                }
            }
        };
        Thread t1 = new Thread(runnable, "线程1");
        Thread t2 = new Thread(runnable, "线程2");
        t1.setPriority(Thread.MAX_PRIORITY);//最大优先级10
        t2.setPriority(Thread.MIN_PRIORITY);//最小优先级1
        t1.start();
        t2.start();
    }
}

运行效果如下

线程优先级根据系统不同,运行时的实现方式不同,并不是设置的越高,CPU就一定会优先执行

synchronized内置锁

对象锁:对象锁锁的是类new出来的对象
类锁:类锁锁的是类的Class对象,Java虚拟机会保证每个类只有一个Class对象

类锁和对象锁运行示例

public class SynchronizedDemo {
    //对象锁
    public synchronized void test1() {
        synchronized (this) {
            //这也是对象锁
            System.out.println("方法中使用对象锁...");
        }
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(600);
            } catch (InterruptedException ie) {
            }
        }
    }

    //类锁
    public synchronized static void test2() {
        synchronized (SynchronizedDemo.class) {
            //这个也是类锁
            System.out.println("方法中使用类锁...");
        }
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(250);
            } catch (InterruptedException ie) {
            }
        }
    }

    public static void main(String[] args) {
        final SynchronizedDemo myt2 = new SynchronizedDemo();
        Thread test1 = new Thread(new Runnable() {
            public void run() {
                myt2.test1();
            }
        }, "test1");
        Thread test2 = new Thread(new Runnable() {
            public void run() {
                SynchronizedDemo.test2();
            }
        }, "test2");
        test1.start();
        test2.start();
    }
}

运行结果

方法中使用对象锁...
test1 : 4
方法中使用类锁...
test2 : 4
test2 : 3
test2 : 2
test1 : 3
test2 : 1
test2 : 0
test1 : 2
test1 : 1
test1 : 0

volatile的使用

volatile不是线程安全的,它能保证数据的可见性,不能保证数据的原子性。
volatile适用于只有一个线程写的,多个线程读的情况下使用,它的并发效率较高。
使用示例:

public class VolatileDemo {
    private volatile int score;

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;

        //this.score = score + 10;//这种做法错误,因为volatile不能保证score的原子性
    }
}

ThreadLocal的使用

多个线程使用ThreadLocal变量的时候,各个线程均使用这个变量的副本,不会访问到其它线程的这个ThreadLocal变量值。

方法 方法说明
public T get() 返回当前线程中ThreadLocal变量副本的值
protected T initialValue() 返回当前线程中ThreadLocal变量副本的初始值
public void remove() 删除当前线程中ThreadLocal变量副本的值
public void set(T value) 设置当前线程中ThreadLocal变量副本的值

下面是ThreadLocal变量和普通变量的区别示例

/**
 * @CalssName UseThreadLocal
 * @Description ThreadLocalDemo
 * @since JDK 1.8
 */
public class UseThreadLocal implements Runnable {
    private int count = 0;
    private ThreadLocal<Integer> threadLocal = new ThreadLocal() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        synchronized (this) {
            for (int i = 1; i <= 3; i++) {
                this.count += this.count + i;
                threadLocal.set(threadLocal.get() + i);
                System.out.println(threadName + " this.count:" + this.count);
                System.out.println(threadName + " threadLocal value:" + threadLocal.get());
            }
        }
    }

    public static void main(String[] args) {
        UseThreadLocal useThreadLocalRunnable = new UseThreadLocal();
        new Thread(useThreadLocalRunnable, "线程1").start();
        new Thread(useThreadLocalRunnable, "线程2").start();
        new Thread(useThreadLocalRunnable, "线程3").start();
    }
}

线程的等待与通知

wait():当前线程处于等待状态,并释放锁,既然要释放锁就必须先获得锁,而线程进入synchronized修饰的方法或者代码块中才能获得锁,所以wait()方法只能在synchronized修饰的方法或者代码块中才能使用。

notify():随机通知并唤醒一个使用wait()方法等待的线程。
notifyAll():通知并唤醒所有使用wait()方法等待的线程。
尽量使用notifyAll()方法,因为使用notify()有可能发生信号丢失的情况。

等待与通知的标准范式

  1. 等待线程获取到对象的锁,调用wait()方法,放弃锁,进入等待队列
  2. 通知线程获取到对象的锁,调用对象的notify()方法
  3. 等待线程接受到通知,从等待队列移到同步队列,进入阻塞状态
  4. 通知线程释放锁后,等待线程获取到锁继续执行

下面是使用wait()、notifyAll和synchronized内置锁做的简易链接池工具

/**
 * @CalssName DBPool
 * @Description 数据库链接池工具
 * @since JDK 1.8
 */
public class DBPool {

    private static LinkedList<Connection> pool = new LinkedList<>();
    private static String url = "jdbc:mysql://192.168.86.101:3306/mysql";
    private static String username = "root";
    private static String password = "my-pwd";

    /**
     * @return java.sql.Connection
     * @Title getConnection
     * @Description 从链接池中获取链接,如果没有链接了在指定有效时间内获取链接,不然返回null
     * @Param [mills] 没有链接时指定获取去时间
     * @Throws InterruptedException
     */
    public Connection getConnection(long mills) throws InterruptedException {
        synchronized (pool) {
            if (mills <= 0) {
                while (pool.isEmpty()) {
                    //链接被用光了,等待回收链接
                    pool.wait();
                }
                //有链接了
                return pool.removeFirst();
            } else {
                long overTime = System.currentTimeMillis() + mills;
                long remain = mills;
                while (pool.isEmpty() && remain > 0) {
                    //没链接了,等待指定时间
                    pool.wait(remain);
                    remain = overTime - System.currentTimeMillis();
                }
//                指定时间后还没有链接就返回null
                if (pool.isEmpty()) return null;
                return pool.removeFirst();
            }
        }
    }

    /**
     * @CalssName DBPool
     * @Description 释放链接
     * @since JDK 1.8
     */
    public void releaseConnection(Connection connection) {
        if (connection != null) {
            synchronized (pool) {
                pool.addLast(connection);
                //链接回收了,通知其他等待的线程使用
                pool.notifyAll();
            }
        }
    }

    /**
     * @CalssName DBPool
     * @Description 初始化链接池
     * @since JDK 1.8
     */
    public DBPool(int initSize) {
        if (initSize > 0) {
            try {
                Class.forName("com.mysql.jdbc.Driver");
                for (int i = 0; i < initSize; i++) {
                    Connection connection = DriverManager.getConnection(url, username, password);
                    pool.add(connection);
                }
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        new DBPool(10);
        System.out.println(pool.size());
    }
}

Join方法

如果线程A调用线程B的join()方法之后就必须等待线程B执行完,线程A才会执行

示例

/**
 * @CalssName ThreadUseJoin
 * @Description 使用join
 * @since JDK 1.8
 */
public class ThreadUseJoin {
    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ",i:" + i);
            }
        }, "t1");
        t1.start();

        Thread t2 = new Thread(() -> {
            try {
                t1.join();//等t1线程执行完销毁才开始执行
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + ",i:" + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        t2.start();
    }
}

Yield方法

yield方法执行后并不会释放锁,而是放弃当前在CPU的运行权力,等待下一次CPU调度执行。

执行sleep和notify、notifyAll方法也不会释放锁,notify、notifyAll执行之后唤醒其他线性,但唤醒的以及其他线程仍然处于阻塞状态,直到当前synchronized修饰的代码执行完其他线程才会获取锁

猜你喜欢

转载自www.cnblogs.com/yhongyin/p/11117749.html