包装类
基本类型 |
对应的包装类(位于java.lang包中) |
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
char |
Character |
boolean |
Boolean |
包装类的三个常用用法:
字符串—>其他类型
Integer.toString();
Integer.parseInt();
Integer.valueOf();
public class Test {
public static void main(String[] args) {
int num = 123;
String str = Integer.toString(num);
double doublenum = Double.parseDouble(str);
long longnum = Long.valueOf(str);
}
}
线程
创建线程的三种方式
继承Thread
public class Main {
//main方法是由主线程执行的,理解成main方法就是一个主线程。
public static void main(String[] args) {
//第三步,new一个MyThread线程类的对象
Thread thread = new MyThread();
//第四部,运行start方法,最终还是执行run方法
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程"+i);
}
}
//第一步,新建一个类继承自Thread
static class MyThread extends Thread{
//第二步,重写run()方法
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("zizizi子线程"+i);
}
}
}
}
启动线程必须调用start()
方法
多线程是并发抢占CPU执行,所以在执行的过程中会出现并发随机性。
Thread的常用API:
public void setName(String name)
给当前线程取名字public void getName()
获取当前线程的名字,主线程默认mainpublic static Thread currentThread()
获取当前线程对象public static void sleep(long time)
让线程休眠time毫秒
通过线程的有参构造取名字:
static class MyThread extends Thread{
MyThread(String name){
super(name);
}
//第二步,重写run()方法
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("子线程"+i);
}
}
}
实现Runnable接口创建线程任务
Thread的构造器:
Thread()
Thread(String name)
Thread(Runnable target)
Thread(Runnable target,String name)
public class RunnableTest {
public static void main(String[] args) {
//第三步,new一个接口实现类的对象,target只是线程任务,并不是线程
Runnable target = new MyThread();
//第四步,new一个Thread线程
Thread t1 = new Thread(target,"线程一");
Thread t2 = new Thread(target,"线程二");
t1.start();
t2.start();
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
//第一步,先继承Runnable接口
static class MyThread implements Runnable{
//第二步,重写run方法
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
实现Runnable接口来创建线程的优点:
- 只是实现了Runable的接口,还可以继承其他类。
- 同一个线程任务对象可以被包装成多个线程对象
- 适合多个线程去共享同一个资源
- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码可以和线程独立
- 线程池可以放入实现Runnable或Callable线程任务对象。
注意:其实Thread类本身也是实现了Runnable接口的。
匿名创建方法:
Thread t = new Thread(new Runnable(){
@Override
public void run(){
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}).start();
实现Callable接口创建线程
public class CallableTest {
public static void main(String[] args) {
//第三步,new一个Callable接口实现类的对象
Callable<String> callable = new MyThread();
//第四步,把Callable任务对象包装成一个未来对象
//未来任务对象其实是一个Runnable的对象,这样就可以被包装成线程对象。
//未来任务对象在线程执行结束后去得到线程执行结果。
FutureTask<String> futureTask = new FutureTask<String>(callable);
//第五步,新建线程
Thread t = new Thread(futureTask);
//启动线程
t.start();
//获取线程运行的结果
try {
String ans = futureTask.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
//第一步,实现一个Callable接口类
static class MyThread implements Callable<String>{
//第二步,实现call方法
public String call() throws Exception {
int sum=0;
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+i);
sum+=i;
}
return sum+"";
}
}
}
实现Callable接口来创建线程的优点:
- 只是实现了Callable的接口,还可以继承其他类。
- 同一个线程任务对象可以被包装成多个线程对象。
- 适合多个线程去共享同一个资源。
- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码可以和线程独立。
- 很适合做线程池的执行任务。
- 可以直接获取线程执行结果。
线程安全问题
多个线程同时操作同一共享资源的时候可能会出现线程安全问题。
线程同步是用来解决线程安全问题的。
同步代码块
- 同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
- 锁对象 可以是任意类型。
- 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
同步方法:
如果方法是实例方法:同步方法默认用this作为锁的对象。
如果方法是静态方法,默认用类名.class作为锁的对象。
Lock锁
java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
- public void lock():加同步锁。
- public void unlock():释放同步锁。
线程状态
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在API中java.lang.Thread.State这个枚举中给出了六种线程状态:
线程状态 |
导致状态发生条件 |
NEW(新建) |
线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread只有线程对象,没有线程特征。 |
Runnable(可运行) |
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。调用了t.start()方法 :就绪 |
Blocked(锁阻塞) |
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) |
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) |
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) |
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
线程池
就是容纳多个线程的容器,省去了频繁创建线程和销毁线程得操作。
线程池在Java中的代表类:ExecutorService
(接口)
静态方法得到一个线程池的对象:
public static ExecutorService newFixedThreadPool(int nThreads);
ExecutorServoce提交线程任务对象执行的方法:
Future<?> submit(Runnable task)
:提交一个Runnable的任务对象给线程池执行
Future<?> submit(Callable task)
:提交一个Callable的任务对象给线程池执行
Runnable做线程池的任务
public class ExecutorTest {
public static void main(String[] args) {
//新建一个Runnable线程任务
Runnable runnable = new Runnable() {
public void run() {
Thread.currentThread().setName("first");
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
};
ExecutorService pools = Executors.newFixedThreadPool(3);
pools.submit(runnable);
pools.submit(runnable);
pools.submit(runnable);
pools.submit(runnable); //会先等待
pools.shutdown();
}
}
Callable做线程池的任务
public class ExecutorTest {
public static void main(String[] args) {
//新建一个线程池,核心任务数为3
ExecutorService pools = Executors.newFixedThreadPool(3);
//提交Callable的任务对象后返回一个未来任务对象
Future<String> t1 = pools.submit(new MyThread(1000));
Future<String> t2 = pools.submit(new MyThread(2000));
Future<String> t3 = pools.submit(new MyThread(3000));
//获取线程池执行的任务的结果
try{
System.out.println(t1.get());
System.out.println(t2.get());
System.out.println(t3.get());
}catch (Exception e){
e.printStackTrace();
}
}
//Callable的实现类
static class MyThread implements Callable<String> {
private int num;
MyThread(int num){
this.num=num;
}
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < num; i++) {
sum+=i;
}
return Thread.currentThread().getName()+"结果为:"+sum;
}
}
}
线程池的作用
如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。
死锁
死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
由于线程被无限期地阻塞,因此程序不可能正常终止。
java 死锁产生的四个必要条件:
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用。
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待循环队列:p1要p2的资源,p2要p1的资源。这样就形成了一个等待环路
当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,
便可让死锁消失
死锁一般存在资源的嵌套请求。
死锁案例
public class ThreadDead {
public static Object resources1 = new Object();
public static Object resources2 = new Object();
public static void main(String[] args) {
//线程1
new Thread(new Runnable() {
@Override
public void run() {
synchronized (resources1){
System.out.println("线程1已经占用资源1请求资源2");
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (resources2){
System.out.println("线程1已经占用资源2");
}
}
}
}).start();
//线程2
new Thread(new Runnable() {
@Override
public void run() {
synchronized (resources2){
System.out.println("线程2已经占用了资源2,开始请求资源1");
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (resources1){
System.out.println("线程2已经占用了资源1");
}
}
}
}).start();
}
}
volatile关键字
先来看一段代码案例:
public class VolatileThreadDemo {
public static void main(String[] args) {
VolatileThread volatileThread = new VolatileThread();
volatileThread.start();
while (true){
if(volatileThread.isFlag()){
System.out.println("执行~~"); //没有输出
}
}
}
}
class VolatileThread extends Thread{
private boolean flag = false;
public boolean isFlag() {
return flag;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.flag = true;
System.out.println("flag="+flag);
}
}
程序中并不会输出“执行~”
不可见性的原因:每个线程都有自己的工作内存,线程都是从主内存拷贝共享变量的副本值。
每个线程是在自己的工作内存中操作共享变量的。
解决方案:
并发编程下,多线程修改变量,会出现线程间变量的不可见性。
解决线程间变量的不可见性方案有两种常见方式:
1.加锁
每次加锁会清空线程自己的工作内存,从新读取内存最新值。
2.对共享的变量进行volatile关键字修饰。
volatile修饰的变量可以在多线程并发修改下,实现线程间变量的可见性。
一旦一个线程修改了volatile修饰的变量,另一个线程可以立即取到最新值。
加锁:
某一个线程进入synchronized
代码块前后,执行过程入如下:
a.线程获得锁
b.清空工作内存
c.从主内存拷贝共享变量最新的值到工作内存成为副本
d.执行代码
e.将修改后的副本的值刷新回主内存中
f.线程释放锁
volatile:
- VolatileThread线程从主内存读取到数据放入其对应的工作内存
- 将flag的值更改为true,但是这个时候flag的值还没有写会主内存
- 此时main方法读取到了flag的值为false
- 当VolatileThread线程将flag的值写回去后,失效其他线程对此变量副本
- 再次对flag进行操作的时候线程会从主内存读取最新的值,放入到工作内存中
总结: volatile保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。
原子性
在一次操作或者多次操作中,所有操作都得到了执行并且不受任何因素的影响,要么就不执行。
先看一段代码案例:
public class VolatileAtomicThread implements Runnable {
private int cnt = 0;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
cnt++;
System.out.println("cnt的值为==》"+cnt);
}
}
}
class VolatileAtomicThreadDemo {
public static void main(String[] args) {
Runnable runnable = new VolatileAtomicThread();
for (int i = 0; i < 100; i++) {
new Thread(runnable).start();
}
}
}
cnt的值并不会每次都加到10000,没有保证原子性。
why?
因为每个线程在工作中是操作共享变量的。
用volatile修饰变量后有没有用?
没用,在volatile更新数据的过程中,可能已经进行了其他的操作,比如i++。
实现原子性的两种方式:
1、加锁 synchronized
,性能差
2、使用原子类 AtomicInteger
概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。
原子型Integer,可以实现原子更新操作
public AtomicInteger(): 初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
int get(): 获取值
int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
public class VolatileAtomicThread implements Runnable {
// 原子类中封装好了整型变量,默认值是0
private AtomicInteger atomicInteger = new AtomicInteger();
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 100 ; x++) {
// 底层变量+1且返回!
System.out.println("count =========>>>> " + atomicInteger.incrementAndGet());
}
}
}
class VolatileAtomicThreadDemo {
public static void main(String[] args) {
// 创建VolatileAtomicThread对象
Runnable target = new VolatileAtomicThread() ;
// 开启100个线程对执行这一个任务。
for(int x = 0 ; x < 100 ; x++) {
new Thread(target).start();
}
}
}
为什么使用原子类可以保证原子性操作,且性能好,而且线程安全?
底层基于CAS乐观锁机制,每次修改数据不会加锁,等到修改的时候再判断有没有别人修改。
CAS
Compare And Swap(比较再交换)
是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。CAS可以将read-modify-check-write
转换为原子操作,这个原子操作直接由处理器保证。
在更新地址值之前,进程中的原先值先与地址值比较
CAS与Synchronized:乐观锁,悲观锁。
CAS和Synchronized都可以保证多线程环境下共享数据的安全性。那么他们两者有什么区别?
Synchronized是从悲观的角度出发(悲观锁)
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。因此Synchronized我们也将其称之为悲观锁。jdk中的ReentrantLock也是一种悲观锁。性能较差!!
CAS是从乐观的角度出发:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。
CAS这种机制我们也可以将其称之为乐观锁。综合性能较好!