大纲:
目录
一、基本概念:
二、线程的创建
1、方法一:继承Thread类
2、创建线程的方法二:实现Runnable接口
Thead和runnable两种创建线程方式对比
三、线程的方法:
四、线程的生命周期:
五、解决多线程安全问题:
方式一:同步代码块
方式二:同步方法
方式三:JDK5.0新增方法:Lock锁
synchronized与Lock的对比:
六、线程之间的通信
七、sleep和wait方法的异同:
八、生产者和消费者问题:
九、新增的创建多线程方式:
1、实现Callable接口
2、使用线程池方式:
1、程序:一段静态的代码
2、进程:是程序的一次执行过程,或者是正在运行的一个程序,是一个动态的过程。有它自身的产生、存在、和消亡的过程——生命周期
3、线程:进程可以进一步细化为线程,是一个程序内部的一条执行路径。
4、并发:多线程
5、并行:多进程
public class ThreadTest06 {
public static void main(String[] args) {
NumberThread t1 = new NumberThread();
// start方法的作用:1.启动当前线程 2.调用当前线程的run()方法
t1.start();
// 问题:再启动一个线程,遍历100以内的偶数,不可以还让已经start()过的线程去执行,否则会报IllegalThreadStateException
NumberThread t2 = new NumberThread();
t2.start();
t1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
}
class NumberThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
}
使用匿名对象、子类的方式创建线程:
public class ThreadTest06 {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
}.start();
}
}
public class ThreadTest08 {
public static void main(String[] args) {
runThread r = new runThread();
Thread t = new Thread(r);
t.start();
}
}
class runThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}
继承Thread类
public class ticketThread {
public static void main(String[] args) {
window1 t1 = new window1("窗口一");
window1 t2 = new window1("窗口二");
window1 t3 = new window1("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class window1 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(getName() + ":" + ticket);
ticket--;
} else {
break;
}
}
}
public window1(String name) {
super(name);
}
}
实现runnable接口:
public class ticketRunnable {
public static void main(String[] args) {
win w = new win();
Thread t1 = new Thread(w, "窗口一");
Thread t2 = new Thread(w, "窗口二");
Thread t3 = new Thread(w, "窗口三");
t1.start();
t2.start();
t3.start();
}
}
class win implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+" " +ticket);
ticket--;
}else {
break;
}
}
}
}
两种方式对比:
开发中优选选择:实现Runnable接口的方式
原因:
1、实现接口的方式没有类的单继承性局限
2、实现的方式更适合来处理多个线程共享数据的情况,例如上面两个代码的ticket。继承Thead的方式必须把ticket声明为static类型,而实现runnable的方式不需要,因为其天生就只有一份
两种方式的联系:翻看Thread的源码就知道,其实Thread类也继承了Runnable接口。
给线程命名方式一:使用setName()方法
public class ThreadTest07 {
public static void main(String[] args) {
NameThread t1 = new NameThread();
t1.setName("线程一");
t1.start();
}
}
class NameThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}
线程命名方式二:重写构造方法
public class ThreadTest07 {
public static void main(String[] args) {
NameThread t1 = new NameThread("线程一");
t1.start();
}
}
class NameThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
public NameThread(String name){
super(name);
}
}
1、yield()方法:暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法。自己也会重新加入竞争
2、join()方法:在线程a中调用b线程的join方法,此时线程a就进入阻塞状态,直到线程b执行完后,线程a才结束阻塞状态
public class ThreadTest07 {
public static void main(String[] args) {
NameThread t1 = new NameThread("线程一");
t1.start();
for (int i = 0; i < 100; i++) {
if(i%2==0) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
if(i==2){
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class NameThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
public NameThread(String name){
super(name);
}
}
3、sleep(long millitime):让当前线程睡眠指定毫秒时长,在指定的millitime毫秒时间内,线程是阻塞状态
4、isAlive() 判断当前线程是否存活
用图总结吧
synchronized (同步监视器){
// 需要被同步的代码
}
说明:
单例模式的线程安全实现方式:
class Dog{
private static Dog instance = null;
private Dog(){
}
public static Dog getInstance(){
if(instance ==null){
synchronized (Dog.class){
if(instance == null){
instance = new Dog();
}
}
}
return instance;
}
}
创建锁的时候可以传入一个boolean类型的参数,若是true则此锁为公平锁,否则为非公平锁公平锁的含义是在外面等待的线程在可以执行后按照先后顺序执行
而synchronized只有非公平锁
public class ticketRunnable {
public static void main(String[] args) {
win w = new win();
Thread t1 = new Thread(w, "窗口一");
Thread t2 = new Thread(w, "窗口二");
Thread t3 = new Thread(w, "窗口三");
t1.start();
t2.start();
t3.start();
}
}
class win implements Runnable {
private int ticket = 100;
// 这里可以传入一个boolean类型的参数,若是true则此锁为公平锁,否则为非公平锁
// 公平锁的含义是在外面等待的线程在可以执行后按照先后顺序执行
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + ticket);
ticket--;
} else {
break;
}
} finally {
lock.unlock();
}
}
}
}
lock还有 如下两种方法:
tryLock(5, TimeUnit.SECONDS);
lockInterruptibly();
优先使用顺序:Lock -> 同步代码块(已经进入了方法体,分配了相应资源)-> 同步方法(在方法体之外)
1、syncronized最后会进入等待队列,不占用cpu。lock一般实现方式是cas(类似于自旋锁)会不停的轮询能不能拿到锁,会占用cpu
2、什么时候用自旋锁什么时候用重量级锁:
a. 线程少-自旋锁 线程多-重量级锁,即进入等待队列
b. 操作消耗时间长-重量级锁
交替打印100以内的数字
public class NumSkip {
public static void main(String[] args) {
Numb n = new Numb();
Thread t1 = new Thread(n,"打印机一:");
Thread t2 = new Thread(n,"打印机二:");
t1.start();
t2.start();
}
}
class Numb implements Runnable{
private int num = 1;
@Override
public void run() {
while (true){
synchronized (this) {
if (num <= 100) {
notify();
try {
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + num);
num++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
涉及到三个方法:
注意:
相同点:都会使得线程进入阻塞状态
不同点:
public class ProducerAndConsumerTest {
public static void main(String[] args) {
Clerks c = new Clerks();
Prod pro = new Prod(c);
Cons con = new Cons(c);
pro.start();
con.start();
}
}
class Clerks {
private int count = 0;
public synchronized void produceThing() {
System.out.println();
if (count < 20) {
count++;
System.out.println(Thread.currentThread().getName() + "开始生产第" + count + "个产品");
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumeThing() {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "开始消费第" + count + "个产品");
count--;
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Prod extends Thread {
private Clerks clerk;
public Prod(Clerks clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + "开始生产产品");
while (true) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceThing();
}
}
}
class Cons extends Thread {
private Clerks clerks;
public Cons(Clerks clerks) {
this.clerks = clerks;
}
@Override
public void run() {
System.out.println(getName() + "开始消费产品");
while (true) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerks.consumeThing();
}
}
}
public class CallTest {
public static void main(String[] args) {
NumThread numThread = new NumThread();
FutureTask futureTask = new FutureTask(numThread);
new Thread(futureTask).start();
try {
// get()返回值即为FutureTask构造器Callable实现类重写call()的返回值
Object sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//
class NumThread implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThredPoolTest {
public static void main(String[] args) {
// 下面这行代码获取的是一个接口的类型(多态)
ExecutorService service1 = Executors.newFixedThreadPool(3);
// 只有将其转换为具体的类才可以对其设置属性
ThreadPoolExecutor service = (ThreadPoolExecutor)service1;
service.setCorePoolSize(6);
service.execute(new NumT());
// 用完关闭线程池
service.shutdown();
}
}
class NumT implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
十、释放锁和不释放锁的操作总结
volatile有两个特性:
public class Singleton2 {
private static volatile Singleton2 INSTANCE;
private Singleton2() {
}
public static Singleton2 getInstance() {
if (INSTANCE == null) {
synchronized (Singleton2.class) {
if(INSTANCE==null) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
INSTANCE = new Singleton2();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 30; i++) {
new Thread(new Runnable() {
public void run() {
System.out.println(Singleton2.getInstance().hashCode());
}
}).start();
}
}
}
volatile不能保证原子性,只能保证可见性
1、锁的粒度缩小
2、锁定某个对象o,如果o的属性发生改变,不影响使用,但是如果O变为另外一个对象,则锁定的对象发生改变。应避免将锁定对象的引用编程另外的对象
public class ChangeLock {
Object o = new Object();
void m(){
synchronized (o){
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
ChangeLock c = new ChangeLock();
new Thread(c::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(c::m,"t2");
// 说对象发生改变,所以t2线程得意执行,如果注释这句话,线程2永远得不到执行机会
// c.o = new Object();
t2.start();
}
}
也有人叫其乐观锁。cas在unsafe类中,可以直接操作内存
凡是Atomic开头的都是通过cas保证线程安全
public class AtomicTest {
AtomicInteger count = new AtomicInteger(0);
void m(){
for (int i = 0; i < 1000; i++) {
count.incrementAndGet();
}
}
public static void main(String[] args) {
AtomicTest t = new AtomicTest();
ArrayList list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new Thread(t::m,"thread-"+i));
}
list.forEach((o)->{o.start();});
list.forEach((o)-> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
ABA问题:
如果要操作的为基本数据类型或者数值类型没问题,如果是引用类型则可能会发生问题。解决方法是加版本号
三种方式的对比:
public class CompareThread {
static long count2 = 0L;
static AtomicLong count1 = new AtomicLong(0L);
static LongAdder count3 = new LongAdder();
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int i1 = 0; i1 < 100000; i1++) {
count1.incrementAndGet();
}
});
}
long start = System.currentTimeMillis();
for (Thread thread : threads) thread.start();
for (Thread thread : threads) thread.join();
long end = System.currentTimeMillis();
System.out.println("count1-Atomic "+count1+" time: "+(end-start));
//------
Object o = new Object();
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int i1 = 0; i1 < 100000; i1++) {
synchronized (o) {
count2++;
}
}
});
}
start = System.currentTimeMillis();
for (Thread thread : threads) thread.start();
for (Thread thread : threads) thread.join();
end = System.currentTimeMillis();
System.out.println("count2 "+count2+" time: "+(end-start));
// --------
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(()->{
for (int i1 = 0; i1 < 100000; i1++) {
count3.increment();
}
});
}
start = System.currentTimeMillis();
for (Thread thread : threads) thread.start();
for (Thread thread : threads) thread.join();
end = System.currentTimeMillis();
System.out.println("count3-LongAdder "+count3+" time: "+(end-start));
}
}
结果对比:
为什么Atomic比synchronized要快,因为synchronized加锁了,有可能会去操作系统申请重量级锁。而Atomic内部使用了cas操作
LongAder为什么比Atomic还要快呢,是因为其内部使用了分段锁