一、概念
1,程序(program):一段静态代码,静态对象
进程(process):是程序一次执行过程,正在运行中的一个程序,有生命周期
线程(thread):进程可以进一步细化为线程,一个程序内部的一条执行路径,每个线程有其独立的栈和程序计数器(pc)。
2,一个java应用程序java.exe,至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程
3,并行与并发:
并行:多个cpu同时执行多个任务A 并发:一个cpu(采用时间片)同时执行多个任务
二、Thread
一:方式一
1,创建一个继承于Thread的类
2,重写Thread类的run() ——将此线程执行的操作声明在run()中
3,创建Thread类的子类对象
4,通过此对象调用start()————启动当前线程,并调用当前线程的run()。
<u>不能通过直接调用run()的方式启动线程</u>**
不能让已经start()的线程再启动start(),否则会报异常。如果需要创建新的线程,在new一个。
创建Thread类的匿名子类的方式:
new Thread(){
public run(){
}
}.start();
方式二:实现Runnable接口
public class MyThread implements Runnable {
int num = 1000;
@Override
public void run() {
for (int i = 1; i <=100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread mth = new MyThread();
Thread th1 = new Thread(mth);
th1.start();
Thread th2 = new Thread(mth);
th2.start();
}
}
方式三:Callable接口
方式四:线程池
三、线程调度
sleep() 休眠
yield() 线程礼让会释放资源,但是我们又会平等的抢资源
join():线程的合并
interrupt() :修改线程状态
四、线程安全
1、Callable接口
其最大的特点:call方法有返回值,并且是要执行完call方法才能拿到返回值,自动阻塞
相比于Runnable里的run方法,其可以有返回值,这样当我们需要一个能返回某个值的线程时,可以使用Callable接口实现。
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
但,当我们直接new出实现Callable接口的类时,无法将其直接传给Thread的构造,因为Thread构造方法里面没有接受Callable的。
这时,我们需要找到能接收Callable的,并其能被Thread的接收----------FutureTask
是因为其继承了runnable和future
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
public class FutureTask<V> implements RunnableFuture<V> {
//两个构造函数
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
使用方式:
//主方法中:
int[] nums1 = new int[nums.length/num];
CallSum cs = new CallSum(nums1);
FutureTask<Integer> ft =new FutureTask<Integer>(cs);
Thread th = new Thread(ft);
th.start();
sums += ft.get(); //可以获得call的返回值,阻塞主程序
//无法通过cs获得,因为Callable中只有一个Call()方法。没有其他方法
class CallSum implements Callable<Integer>{
int[] nums;
//构造函数
public CallSum(int[] nums) {
this.nums = nums;
}
//call()方法
public Integer call() throws Exception {
int sum = 0;
for(int i=0;i<nums.length;i++) {
sum+=nums[i];
}
return sum;
}
2、synchronized
修饰代码块:一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象该代码块的线程将被阻塞
public class TestTicket {
public static void main(String[] args) {
Site site = new Site();
new Thread(site,"金吒").start();
new Thread(site,"木吒").start();
new Thread(site,"哪吒").start();
}
}
//模拟卖票网站
class Site implements Runnable{
int num = 10;//总票数
int count = 0;//第几张票
public void run() {
while(true) {
synchronized (this) {
if (num <= 0) {//没有票
break;
}
//有票
num--;
count++;
System.out.println(Thread.currentThread().getName() + "买到了第" + count + "张票,还剩余" + num + "张票");
}
}
}
}
修饰方法
同步方法
public class TestTicketMethod {
public static void main(String[] args) {
Site1 site = new Site1();
new Thread(site,"金吒").start();
new Thread(site,"木吒").start();
new Thread(site,"哪吒").start();
}
}
//模拟卖票网站
class Site1 implements Runnable{
int num = 10;//总票数
int count = 0;//第几张票
boolean flag = true;
public void run() {
while(flag) {
sale();
}
}
//同步方法
public synchronized void sale() {
if (num <= 0) {//没有票
flag=false;
return;
}
//有票
num--;
count++;
System.out.println(Thread.currentThread().getName() + "买到了第" + count + "张票,还剩余" + num + "张票");
}
}
3、线程不安全的场景
饿汉单例模式
public class Student {
private Student() {
}
private static Student s = null;
public static Student getInstance() {
if (s == null) {
s = new Student();
}
return s;
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
List<Student> stus = new ArrayList<Student>();
for(int i=1;i<=5;i++) {
new Thread(new Runnable() {
public void run() {
Student s = Student.getInstance();
stus.add(s);
}
}).start();
}
Thread.sleep(2000);
System.out.println(stus);
}
}
ArrayList
HashMap
Hashtable:安全,效率低
ConcurrentHashMap:安全效率高,并发需要保证安全的时候。
4、死锁
public class TestDead {
public static void main(String[] args) {
new Thread(new Dead(130,131)).start();
new Thread(new Dead(131,130)).start();
}
}
class Dead implements Runnable{
Integer a;
Integer b;
public Dead(Integer a,Integer b) {
this.a = a;
this.b = b;
}
public void run() {
synchronized (a) {
System.out.println(Thread.currentThread().getName()+"获取到"+a);
synchronized (b) {
System.out.println(Thread.currentThread().getName()+"获取到"+b);
}
}
}
}
注意,这里一共只有两把锁,而不是四把锁
5、线程池
5.1
顶级接口:
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
//自定义线程池
public class ThreadPoolExecutor extends AbstractExecutorService {}
//构造函数
public ThreadPoolExecutor(int corePoolSize,//核心池子大小
int maximumPoolSize,//池子最大大小
long keepAliveTime,//保持活着的时间
TimeUnit unit,//时间单位
//阻塞队列
BlockingQueue<Runnable> workQueue) {
}
new ThreadPoolExecutor.AbortPolicy()://拒绝任务并抛出异常
new ThreadPoolExecutor.DiscardPolicy()://拒绝并且不抛异常
new ThreadPoolExecutor.DiscardOldestPolicy()://拒绝排队时间最长的任务
new ThreadPoolExecutor.CallerRunsPolicy()://把任务交给调用者执行
5.2自动创建线程池
//创建单线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
特点:核心线程数=最大线程数=1,队列是无界队列
存在问题:可能让内存溢出
//固定长度的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点:核心线程数=最大线程数=n,队列是无界队列
存在问题:可能让内存溢出
//带缓存的线程池
ExecutorService pool = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点:核心线程为0,最大线程数无界,队列是同步队列(不能存储任务)
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
特点: 核心线程指定,最大线程无界
队列:对任务按时间进行排序
pool.scheduleAtFixedRate(r, 1000, 2000, TimeUnit.MILLISECONDS);
6、ThreadLocal(线性局部变量)
ThreadLocal解决安全和效率的问题
static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String getDate(int i) {
SimpleDateFormat sdf = tl.get();//获取当前线程的sdf对象
System.out.println(Thread.currentThread().getName()+"---"+System.identityHashCode(sdf));
String result="";
result = sdf.format(new Date(i*1000));
return result;
}
在并发编程中,如果成员变量不做任何处理是不安全的,因为会出现各个线程都来操作一个成员变量的情况。
这种情况之下ThreadLocal就非常适合使用:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。
ThreadLocal<String> localVar = new ThreadLocal<>();
//Thread 的里面(成员变量):这两个变量都是调的ThreadLocal里面的内部类
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//ThreadLocal类
public class ThreadLocal<T> {
//有两个内部类
//第一个省·····
//第二个是ThreadLocal里面的内部类,,ThreadLocalMap
//其Map里面又有一个内部类Entry,键值对
static class ThreadLocalMap { //这个内部类类似于hashmap,不够不是链表实现,是数组实现的
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
//键值对的构造函数
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//构造函数
public ThreadLocal() {
}
}
//get方法
public T get() {
// 这里我们基本上可以找到ThreadLocal数据隔离的真相了,每个线程Thread都维护了自己的threadLocals变量,所以在每个线程创建ThreadLocal的时候,实际上数据是存在自己线程Thread的threadLocals变量里面的,别人没办法拿到,从而实现了隔离。
Thread t = Thread.currentThread();//获取当前线程对象---1
ThreadLocalMap map = getMap(t);//通过当前线程获取ThreadLocalMap的对象---》Thread类的成员变量---2
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();//3---执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
}
ThreadLocalMap getMap(Thread t) {//return 线程的成员变量
return t.threadLocals;
//ThreadLocal.ThreadLocalMap threadLocals = null;默认为空
}
private T setInitialValue() {
T value = initialValue();//如果子类重写这个方法,就去调用子类的方法得到值,如果没重写,就得到null
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//还是为null
if (map != null)
map.set(this, value);
else
createMap(t, value);//创建map
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
//创建的同时添加
}
//set方法
public void set(T value) {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//以当前线程作为key值,找到对应的map
if (map != null)
map.set(this, value);
else //为null,说明是首次添加,需要创建对应的map
createMap(t, value);
}
//删除---remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
//每个线程的内部都有一个这个:ThreadLocal.ThreadLocalMap threadLocals = null;
//里面是数组类型的键值对,key为当前线程,value为我们使用set设置的值。每个线程维护了自己的threadlocals变量,这样每个线程只能访问自己线程里的变量,避免了线程对某一个变量的同时重写。不过这些线程需要remove掉,不然会一直存在导致内存溢出。
7、生产者消费者
public void save() throws InterruptedException {
while(money>=1000) {
System.out.println(Thread.currentThread().getName()+"发现卡中钱没取,等等再存");
//sleep--wait---锁来调--释放锁
this.wait();
}
this.setMoney(this.getMoney()+1000);
System.out.println(Thread.currentThread().getName()+"存入了1000元,目前卡的余额是"+this.getMoney());
this.notifyAll();//唤醒等待的线程去获取锁来操作
}
public void take() throws InterruptedException {
while(money<=0) {
System.out.println(Thread.currentThread().getName()+"发现卡中没钱,等等再取");
//sleep--wait---锁来调--释放锁
this.wait();
}
this.setMoney(this.getMoney()-1000);
System.out.println(Thread.currentThread().getName()+"取出了1000元,目前卡的余额是"+this.getMoney());
this.notifyAll();
}
这里面ThreadLocal很重要,在编程里尽可能多的运用,加深理解。