java多线程

进程:正在运行的程序,是系统进行资源分配的基本单位。

线程:轻量级进程,是进程中的一条执行路径,也是CPU的基本调度单位。

进程与线程的关系:一个程序运行后至少有一个进程,进程由一个或者多个线程组成,进程间不能共享数据段地址,但是同进程的线程能共享数据段地址。

线程的组成部分包括CPU时间片,运行数据与逻辑代码:

        cpu时间片:OS会给每个线程分配执行时间。

        运行数据:

                堆空间:存储线程使用的对象,多个线程可以共享堆中的对象。

                栈空间:存储线程需要使用到的局部变量,每个线程都拥有独立的栈。

线程的特点:

        1. 抢占式执行:可防止单一线程长时间占用cpu,效率高。

        2.在单核cpu中,宏观上同时执行,微观上顺序执行。

创建线程的方式:

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

        2.实现Runnable接口

        3. jdk1.5之后,实现Callable接口

Callable接口:再JDK1.5以后加入,和Runnable接口类似,实现之后表示一个线程任务,但是它具有泛型返回值,可以声明异常。

public interface Callable<V> {

        public V call() throws Execption;

}

Callable与Runnable的区别:

区别CallableRunnable
方法返回值call方法有返回值run方法无返回值
方法声明call方法有声明异常run方法没有异常

 线程的同步与异步:

同步是指调用某个方法,必须等待该方法返回,才能往下走。

异步:调用某个方法,就像是一次通知,调用者通知该方法后,调用者继续往下执行,同时该方法也在和调用者竞争时间片,二者并发执行。


常用方法:

        1.休眠:当前线程休眠millis毫秒。

        public static void sleep(long millis);

        2.放弃:当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。

        public static void yield();

        3.加入:允许其他线程加入到当前线程中,并阻塞当前线程,直到加入的线程运行完毕,才会运行当前线程。这个是非静态方法,因此需要创建对象。

        public final void join();

        4.优先级:线程优先级为1-10,默认为5,优先级越高(值越大),表示获取cpu的机会越多。

        设置优先级为i:线程对象.setPriority(i);

        5.守护线程:线程有两类,一类是用户线程(前台线程),一类是守护线程(后台线程)。如果程序中的所有前台线程都执行完毕了,后台线程会自动结束。垃圾回收器就属于守护线程。

        设置守护线程:线程对象.setDaemon(true);

        守护线程demo如下,在主线程中将该线程设置为守护线程,主线程运行完毕后,守护线程也会结束:

                守护线程:

package com.test5;

public class DeamonThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "----------" + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

                主线程:

package com.test5;

public class TestDeamon {
    public static void main(String[] args) {
        DeamonThread deamonThread = new DeamonThread();

        // 设置为守护线程
        deamonThread.setDaemon(true);

        deamonThread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程===========" + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 java多线程的安全问题:

临界资源是指共享资源(同一对象),一次仅允许一个线程使用,才可以保证其正确性。原子操作则是指不可分割的多步操作,被视为一个整体,其顺序与步骤不可打乱或者缺省。当多个线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。

1.在应用程序中,用synchronized保证线程安全性的方法:

a. 同步代码块:

        这里面的临界资源必须是互斥锁,具有唯一性,比如类对象等。

synchronized (临界资源){ // 对临界资源对象枷锁
    // 代码(原子操作)
}

b.同步方法:

synchronized 返回值 方法名称(形参列表()) { // 对当前对象(this)加锁
    // 代码(原子操作)
}

 同步规则:

只有在调用同步代码块的方法或者使用同步方法时,才需要对象的锁标记,如果调用不包含同步代码块的方法或或者使用普通方法时,不需要锁标记,可直接调用。

JDK中线程安全的类:

StringBuffer,Vector,Hashtable(这些方法中都包含了synchronized修饰的同步方法)。

要注意的是,同步操作对程序性能有影响。

2.lock接口:

这个是JDK5以后加入的,它的效率比synchronized高,功能更强大,是显示定义,结构更灵活。

lock常用方法:

获取锁,如果锁被占用,则等待其释放(阻塞):void lock()

尝试获取锁,成功返回true,失败返回false,不阻塞:Boolean tryLock();

释放锁:unLock()

示例代码如下:

Lock l = ...;
l.lock();
try {
    ...;
} finally {
    l.unlock();
};

 a. 重入锁ReentrantLock的demo1:

调用类:

package com.test11;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyList {
    // 创建锁
    private Lock lock = new ReentrantLock();
    private String[] str = {"I" , "liek", "", ""};
    private int count = 2;

    public void add (String string) {
        lock.lock();
        try {
            str[count] = string;
            count++;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            lock.unlock();
        }
    }

    public void prnintStr() {
        for (String s : str) {
            System.out.println(s);
        }
    }
}

主类:

package com.test11;

public class TestMylist {
    public static void main(String[] args) {
        final MyList myList = new MyList();

        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                myList.add("haha");
            }
        };

        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                myList.add("xixi");
            }
        };

        Thread thread1 = new Thread(runnable1, "haha");
        Thread thread2 = new Thread(runnable2, "xixi");

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        myList.prnintStr();
    }
}

b. ReentrantReadWriteLock读写锁:

支持一写多读,读写分离,读锁不互斥,写锁互斥,可分别配置读锁,写锁,可使得多个读操作并发执行。一般用在读数据线程较多的场合,在该场合中,在保证线程安全的前提下,读写锁比互斥锁效率更高。

互斥规则:

两个写线程:互斥,阻塞。

读线程与写线程:读阻塞写,写阻塞读,互斥。

两个读线程:不互斥。

demo如下:

读写类:

package com.test13;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MyList {
    ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.ReadLock rl = rwl.readLock();
    ReentrantReadWriteLock.WriteLock wl = rwl.writeLock();
    private int value;

    // 读数据
    public int getValue() {
        rl.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return value;
        } finally {
          rl.unlock();
        }
    }

    // 写数据
    public void setValue(int value) {
        wl.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.value = value;
        } finally {
            wl.unlock();
        }
    }
}

调用线程:

package com.test13;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestMyList {
    public static void main(String[] args) {
        final MyList myList = new MyList();

        Runnable runSet = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "set value");
                myList.setValue(1);
            }
        };

        Runnable runGet = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "get value");
                myList.getValue();
            }
        };

        ExecutorService executorService = Executors.newFixedThreadPool(30);
        for (int i = 0; i < 3; i++) {
            executorService.submit(runSet);
        }

        for (int i = 0; i < 20; i++) {
            executorService.submit(runGet);
        }

        executorService.shutdown();
    }
}

继承Thread类,重写run方法创建线程的demo展示:

获取与修改线程相关信息:

1. 获取线程id与name:

        a.在Thread的子类中调用this.getId与this.getName方法

        b.使用Thread.currentThread().getId()与Thread.currentThread().getName()方法

2. 修改线程name:

        a.使用线程对象的setName方法

        b.使用线程子类的构造方法赋值

3. demo1:

        a. 继承Thread,重写run方法:

package com.test1;

public class MyThread extends Thread {
    // 创建无参构造方法
    public MyThread() {
    }

    // 创建有参构造方法,将参数传给Thread
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            // 方法1:使用继承的Thread中的方法,获取线程的id与name
            // System.out.println("线程id:"+this.getId() + "线程名称" + this.getName() +"hello " + i);
            // 方法2:使用Thread.currentThread().getId()
            System.out.println("线程id: " + Thread.currentThread().getId() + "线程name :" + Thread.currentThread().getName());
        }
    }
}

        b.创建线程对象,启动线程:

package com.test1;

public class test {
    public static void main(String[] args) {
        // 设置线程对象名称的方法1
        MyThread myThread = new MyThread("线程名称修改1");
        // 设置线程对象名称的方法2
        // myThread.setName("线程1");
        myThread.start();
    }
}

4. demo2:多个线程的运行:

        a.继承Thread方法,运行run方法:

package com.test2;

public class TicketSell extends Thread {
    private int ticket = 100;
    public TicketSell() {
    }

    public TicketSell(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (true) {
            if (ticket <= 0) {
                break;
            }
            System.out.println(Thread.currentThread().getName() +"卖了第" + ticket +"张票");
            ticket--;
        }
    }
}

        b.创建线程对象,启动线程:

package com.test2;

public class TestTicketSell {
    public static void main(String[] args) {
        TicketSell ticketSell1 = new TicketSell("售票处1");
        TicketSell ticketSell2 = new TicketSell("售票处2");
        TicketSell ticketSell3 = new TicketSell("售票处3");
        TicketSell ticketSell4 = new TicketSell("售票处4");
        ticketSell1.start();
        ticketSell2.start();
        ticketSell3.start();
        ticketSell4.start();
    }
}

实现Runnable接口的demo展示:

demo:

        a.实现Runnable接口,覆盖run方法

package com.test3;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "hello " + i);
        }
    }
}

        b.创建实现类对象,创建线程对象,并将实现类对象做为线程对象的参数,再调用start方法

package com.test3;

import com.test1.MyThread;

public class Test {
    public static void main(String[] args) {
        // 创建MyRunnable对象,表示线程要执行的功能
        MyRunnable myRunnable = new MyRunnable();
        // 创建线程对象
        Thread thread1 = new Thread(myRunnable, "线程1");
        // 启动线程对象
        thread1.start();
    }
}

如果a步骤中的Runnable接口的只想在当前环境中使用,不想单独建一个类来使用,通过匿名内部类的方式将上面a,b放到一起实现:

package com.test3;

import com.test1.MyThread;

public class Test {
    public static void main(String[] args) {
//        // 创建MyRunnable对象,表示线程要执行的功能
//        MyRunnable myRunnable = new MyRunnable();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "你好 " + i);
                }
            }
        };

        // 创建线程对象
        Thread thread1 = new Thread(runnable, "线程1");
        // 启动线程对象
        thread1.start();
    }
}

Callable接口的demo展示:

package com.test9;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class callableDemo {
    public static void main(String[] args) {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName() + "计算1到10的乘积:");
                int sum = 1;
                for (int i = 1; i <= 10; i++) {
                    sum *= i;
                }
                return sum;
            }
        };

        // 将callable转为一个可执行的任务
        FutureTask<Integer>  futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask, "Thread");
        thread.start();

        // 获取线程返回的乘积结果
        try {
            Integer integer = futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

该接口单独调用时,需要进行转换,因此,一般结合线程池使用。


线程通信:

等待方法:一个线程,在调用obj.wait()时,此线程会释放其拥有的所有锁标记,同时,此线程阻塞在等待队列中。

        public final void wait()

        public final void wati(long timeout)

通知方法:发送通知,唤醒线程。

public final void  notify()

public final void notifyAll()

如下是一个存钱取钱的demo,有多个线程存钱,多个线程取钱:

package com.test7;

public class BankCard {
    private double money;
    private boolean flag; // true:进行取钱操作;false:进行存钱操作

    public BankCard(double money) {
        this.money = money;
        this.flag = false;
    }

    // false:进行存钱操作
    public synchronized void save(double m) { // this作为临界资源
        while(flag) { // flag=true,进行的时取钱操作,存钱操作暂停
            try {
                this.wait(); // 进入等待队列,同时释放cpu等资源
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        money += m;
        System.out.println(Thread.currentThread().getName() + "存了" + m + "元, 余额是:" + money);

        // 存钱操作完毕,可以取钱
        flag = true;
        // 唤醒取钱线程
        this.notifyAll();
    }

    // true:进行取钱操作
    public synchronized void take(double m) {
        while (!flag) { // flag=false,进行存钱操作,取钱暂停
            try {
                this.wait(); //  进入等待状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        money -= m;
        System.out.println(Thread.currentThread().getName() + "取了" + m + "元, 余额是:" + money);

        // 取钱操作完毕,可以存钱
        flag = false;
        // 唤醒存钱线程
        this.notifyAll();
    }
}

主函数:

package com.test7;

public class BankCardTest {
    public static void main(String[] args) {
        final BankCard card = new BankCard(10);
        // 创建两个方法
        Runnable saveMoney = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    int m = 1000;
                    card.save(m);
                }
            }
        };

        Runnable takeMoney = new Runnable() {
            @Override
            public  void run() {
                for (int i = 0; i < 10; i++) {
                    int m = 1000;
                    card.take(m);
                }
            }
        };

        // 创建线程
        Thread threadA = new Thread(saveMoney, "haha_0");
        Thread threadB = new Thread(takeMoney, "xixi_0");
        Thread threadA_1 = new Thread(saveMoney, "haha_1");
        Thread threadB_1 = new Thread(takeMoney, "xixi_1");

        threadA.start();
        threadB.start();
        threadA_1.start();
        threadB_1.start();
    }
}

        上面也是一个经典的生产者与消费者问题的处理方式:若干个生产者生产产品,若干个消费者消费产品。生产与消费的动作是并发执行,即存在一个空间,生产者在往这个空间存放物品的时候,消费者从这个空间搬走物。但是不能出现消费者到一个空的空间搬走物品,也不允许生产者往一个满的空间存放物品。


在多线程中,SimpleDateFormat存在线程安全问题,需要加所持能避免,可用DateTimeFormatter(以上从java 8 开始支持)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值